@defend-tech/opencode-optima 0.1.60 → 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.
@@ -52,7 +52,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
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
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.opencode.base_url` for legacy OpenCode session/prompt delivery, not as the preferred worktree creation API when `clickup.openchamber.base_url` is configured.
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
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`.
57
57
  - Do not update the principal `dev` workspace `.optima/tasks/current.md` for ClickUp task planning.
58
58
  - Unrelated active tasks in `.optima/tasks/current.md` must not block planning; move to/create the correct task worktree instead.
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;
@@ -9234,30 +9235,36 @@ function openChamberUrl(baseUrl, pathname, query = {}) {
9234
9235
  }
9235
9236
  return url.toString();
9236
9237
  }
9237
- async function readOpenChamberJson(response, endpoint) {
9238
+ async function readOpenChamberJson(response, endpoint, serviceName = "OpenChamber") {
9238
9239
  const text = await response.text();
9239
9240
  let data = null;
9240
9241
  if (text.trim()) {
9241
9242
  try {
9242
9243
  data = JSON.parse(text);
9243
9244
  } catch {
9244
- throw new Error(`OpenChamber ${endpoint} returned non-JSON response.`);
9245
+ throw new Error(`${serviceName} ${endpoint} returned non-JSON response.`);
9245
9246
  }
9246
9247
  }
9247
9248
  if (!response.ok) {
9248
9249
  const message = data?.error?.message || data?.message || data?.data?.message || text.slice(0, 200) || `HTTP ${response.status}`;
9249
- throw new Error(`OpenChamber ${endpoint} failed: ${response.status} ${message}`);
9250
+ throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
9250
9251
  }
9251
9252
  return data;
9252
9253
  }
9253
- async function requestOpenChamberJson({ baseUrl, endpoint, method = "GET", directory, body, fetchImpl = globalThis.fetch } = {}) {
9254
- if (typeof fetchImpl !== "function") throw new Error("OpenChamber worktree provisioning requires fetch.");
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.`);
9255
9256
  const response = await fetchImpl(openChamberUrl(baseUrl, endpoint, { directory }), {
9256
9257
  method,
9257
9258
  headers: body ? { "content-type": "application/json" } : void 0,
9258
9259
  body: body ? JSON.stringify(body) : void 0
9259
9260
  });
9260
- return readOpenChamberJson(response, endpoint);
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" });
9261
9268
  }
9262
9269
  function normalizeOpenChamberCollection(value) {
9263
9270
  if (Array.isArray(value)) return value;
@@ -9296,39 +9303,40 @@ function openChamberListIncludesBranch(list, directory, branch) {
9296
9303
  return !entryBranch || entryBranch === branch;
9297
9304
  });
9298
9305
  }
9299
- async function findOpenChamberProject({ baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9300
- const projects = await requestOpenChamberJson({ baseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
9306
+ async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9307
+ const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
9301
9308
  if (!Array.isArray(projects)) return null;
9302
9309
  const resolvedBase = path5.resolve(baseWorktree);
9303
9310
  return projects.find((project) => path5.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
9304
9311
  }
9305
- async function refreshOpenChamberProjectCopy({ baseUrl, baseWorktree, projectId, fetchImpl = globalThis.fetch } = {}) {
9312
+ async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
9306
9313
  if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
9307
- const response = await fetchImpl(openChamberUrl(baseUrl, `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, {}), { method: "POST" });
9308
- await readOpenChamberJson(response, "/experimental/project/{projectID}/copy/refresh");
9314
+ await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, method: "POST", fetchImpl });
9309
9315
  return { refreshed: true, projectId };
9310
9316
  }
9311
- async function listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9312
- return requestOpenChamberJson({ baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
9317
+ async function listOpenChamberGitWorktrees({ openchamberBaseUrl, baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9318
+ return requestOpenChamberJson({ baseUrl: openchamberBaseUrl || baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
9313
9319
  }
9314
- async function verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9315
- const worktrees = await listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl });
9320
+ async function verifyOpenChamberGitWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9321
+ const worktrees = await listOpenChamberGitWorktrees({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, baseWorktree, fetchImpl });
9316
9322
  const verified = openChamberListIncludesBranch(worktrees, worktreePath, branch);
9317
9323
  if (!verified) {
9318
9324
  throw new Error(`OpenChamber Git worktree verification failed for ${worktreePath} on ${branch}.`);
9319
9325
  }
9320
9326
  return { worktree: true, branch: true };
9321
9327
  }
9322
- async function syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9323
- const gitWorktree = await verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9324
- await requestOpenChamberJson({ baseUrl, endpoint: "/experimental/workspace/sync-list", method: "POST", directory: baseWorktree, fetchImpl });
9325
- const project = await findOpenChamberProject({ baseUrl, baseWorktree, fetchImpl });
9326
- if (!project?.id) throw new Error("OpenChamber project was not found after workspace sync; refusing to treat worktree as visible.");
9327
- await refreshOpenChamberProjectCopy({ baseUrl, baseWorktree, projectId: project.id, fetchImpl });
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 });
9328
9336
  const [worktrees, workspaces, directories] = await Promise.all([
9329
- listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl }),
9330
- requestOpenChamberJson({ baseUrl, endpoint: "/experimental/workspace", directory: baseWorktree, fetchImpl }),
9331
- requestOpenChamberJson({ baseUrl, endpoint: `/project/${encodeURIComponent(project.id)}/directories`, directory: baseWorktree, fetchImpl })
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 })
9332
9340
  ]);
9333
9341
  const visibility = {
9334
9342
  worktree: openChamberListIncludesBranch(worktrees, worktreePath, branch),
@@ -9338,7 +9346,7 @@ async function syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktr
9338
9346
  projectId: project.id
9339
9347
  };
9340
9348
  if (!visibility.worktree || !visibility.workspace || !visibility.projectDirectory) {
9341
- throw new Error(`OpenChamber visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
9349
+ throw new Error(`OpenCode visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
9342
9350
  }
9343
9351
  return visibility;
9344
9352
  }
@@ -9347,12 +9355,13 @@ function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {
9347
9355
  throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
9348
9356
  }
9349
9357
  }
9350
- async function createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9358
+ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9359
+ const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
9351
9360
  const worktreeName = clickUpOpenChamberWorktreeName(branch);
9352
9361
  const body = { name: worktreeName, mode: branchExists ? "existing" : "new", worktreeName, branchName: branch, startRef: branchExists ? branch : startPoint };
9353
9362
  let created;
9354
9363
  try {
9355
- created = await requestOpenChamberJson({ baseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
9364
+ created = await requestOpenChamberJson({ baseUrl: effectiveOpenChamberBaseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
9356
9365
  } catch (error) {
9357
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}`);
9358
9367
  }
@@ -9364,15 +9373,17 @@ async function createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch,
9364
9373
  if (createdBranch !== branch) {
9365
9374
  throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
9366
9375
  }
9367
- const verified = await verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
9376
+ const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
9368
9377
  return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
9369
9378
  }
9370
- async function registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9379
+ async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9371
9380
  assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
9372
- const visibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9381
+ const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9373
9382
  return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
9374
9383
  }
9375
- async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, baseUrl = "", fetchImpl = globalThis.fetch, log = null } = {}) {
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;
9376
9387
  const effectiveParent = parentTaskId || taskId;
9377
9388
  const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
9378
9389
  const parentBranch = isSubtask ? deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : "";
@@ -9380,13 +9391,13 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9380
9391
  const prTarget = parentBranch || "dev";
9381
9392
  const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
9382
9393
  if (existing) {
9383
- const registered = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
9394
+ const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
9384
9395
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "metadata_reuse", visibility: registered.openChamber.visibility });
9385
9396
  return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9386
9397
  }
9387
9398
  const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9388
9399
  if (fs5.existsSync(worktreePath)) {
9389
- const registered = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
9400
+ const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
9390
9401
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
9391
9402
  return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9392
9403
  }
@@ -9394,40 +9405,54 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9394
9405
  if (isSubtask) {
9395
9406
  const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
9396
9407
  if (fs5.existsSync(parentWorktree)) {
9397
- const registeredParent = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
9408
+ const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
9398
9409
  parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
9399
9410
  } else {
9400
9411
  const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9401
9412
  const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
9402
- const createdParent = await createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, branchExists: parentBranchExists, fetchImpl });
9403
- const parentVisibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath: createdParent.worktree, branch: parentBranch, fetchImpl });
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 });
9404
9415
  parentBootstrap = { branch: parentBranch, worktree: createdParent.worktree, startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists, provider: "openchamber", visibility: parentVisibility };
9405
9416
  log?.({ type: "openchamber_worktree_created", taskId: effectiveParent, branch: parentBranch, worktree: parentBootstrap.worktree, startPoint: parentBootstrap.startPoint, visibility: parentVisibility });
9406
9417
  }
9407
9418
  }
9408
9419
  const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9409
9420
  const branchExists = clickUpGitRefExists(baseWorktree, branch, runGitFn);
9410
- const created = await createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists, fetchImpl });
9411
- const visibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath: created.worktree, branch, fetchImpl });
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 });
9412
9423
  log?.({ type: "openchamber_worktree_created", taskId, branch, worktree: created.worktree, startPoint: branchExists ? branch : startPoint, visibility });
9413
9424
  return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility } };
9414
9425
  }
9415
9426
  async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", opencodeBaseUrlConfigured = false, openchamberBaseUrl = "", openchamberBaseUrlConfigured = false, clickupClient = null, webhookWorktree = process.cwd(), fetchImpl = globalThis.fetch, ...options } = {}) {
9416
- void opencodeBaseUrl;
9417
9427
  void opencodeBaseUrlConfigured;
9418
9428
  if (!openchamberBaseUrlConfigured || !openchamberBaseUrl) return ensureClickUpTaskWorktree(options);
9419
9429
  try {
9420
- return await ensureClickUpTaskWorktreeOpenChamber({ ...options, baseUrl: openchamberBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
9430
+ return await ensureClickUpTaskWorktreeOpenChamber({ ...options, openchamberBaseUrl, opencodeBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
9421
9431
  } catch (error) {
9422
9432
  const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
9423
9433
  appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
9424
9434
  if (typeof clickupClient?.postTaskComment === "function") {
9425
- try {
9426
- await clickupClient.postTaskComment({ taskId: options.taskId, comment: `${message}
9435
+ const taskId = options.taskId || "unknown task";
9436
+ const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
9437
+ const comment = `${message}
9427
9438
 
9428
- Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenChamber Git API compatibility.` });
9429
- } catch (commentError) {
9430
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
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 });
9431
9456
  }
9432
9457
  }
9433
9458
  throw new Error(message);
@@ -10227,23 +10252,29 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
10227
10252
  if (recent.includes(eventKey)) return { duplicate: true, state };
10228
10253
  return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
10229
10254
  }
10230
- function readClickUpCommentLedger(ledgerPath) {
10231
- const processed = /* @__PURE__ */ new Set();
10232
- if (!ledgerPath || !fs5.existsSync(ledgerPath)) return processed;
10255
+ function readClickUpCommentLedgerEntries(ledgerPath) {
10256
+ if (!ledgerPath || !fs5.existsSync(ledgerPath)) return [];
10233
10257
  try {
10234
10258
  const raw = fs5.readFileSync(ledgerPath, "utf8");
10259
+ const entries = [];
10235
10260
  for (const [index, line] of raw.split(/\r?\n/).entries()) {
10236
10261
  if (!line.trim()) continue;
10237
10262
  try {
10238
- const entry = JSON.parse(line);
10239
- if (entry?.key) processed.add(String(entry.key));
10263
+ entries.push(JSON.parse(line));
10240
10264
  } catch (error) {
10241
10265
  throw new Error(`malformed ledger row ${index + 1}: ${error.message}`);
10242
10266
  }
10243
10267
  }
10268
+ return entries;
10244
10269
  } catch (error) {
10245
10270
  throw new Error(`ClickUp comment ledger unavailable: ${error.message}`);
10246
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
+ }
10247
10278
  return processed;
10248
10279
  }
10249
10280
  function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
@@ -10309,6 +10340,35 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
10309
10340
  recordedAt: at.toISOString()
10310
10341
  });
10311
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
+ }
10312
10372
  function clickUpTaskIdFromPayload(payload = {}) {
10313
10373
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
10314
10374
  }
@@ -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;
@@ -9241,30 +9242,36 @@ function openChamberUrl(baseUrl, pathname, query = {}) {
9241
9242
  }
9242
9243
  return url.toString();
9243
9244
  }
9244
- async function readOpenChamberJson(response, endpoint) {
9245
+ async function readOpenChamberJson(response, endpoint, serviceName = "OpenChamber") {
9245
9246
  const text = await response.text();
9246
9247
  let data = null;
9247
9248
  if (text.trim()) {
9248
9249
  try {
9249
9250
  data = JSON.parse(text);
9250
9251
  } catch {
9251
- throw new Error(`OpenChamber ${endpoint} returned non-JSON response.`);
9252
+ throw new Error(`${serviceName} ${endpoint} returned non-JSON response.`);
9252
9253
  }
9253
9254
  }
9254
9255
  if (!response.ok) {
9255
9256
  const message = data?.error?.message || data?.message || data?.data?.message || text.slice(0, 200) || `HTTP ${response.status}`;
9256
- throw new Error(`OpenChamber ${endpoint} failed: ${response.status} ${message}`);
9257
+ throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
9257
9258
  }
9258
9259
  return data;
9259
9260
  }
9260
- async function requestOpenChamberJson({ baseUrl, endpoint, method = "GET", directory, body, fetchImpl = globalThis.fetch } = {}) {
9261
- if (typeof fetchImpl !== "function") throw new Error("OpenChamber worktree provisioning requires fetch.");
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.`);
9262
9263
  const response = await fetchImpl(openChamberUrl(baseUrl, endpoint, { directory }), {
9263
9264
  method,
9264
9265
  headers: body ? { "content-type": "application/json" } : void 0,
9265
9266
  body: body ? JSON.stringify(body) : void 0
9266
9267
  });
9267
- return readOpenChamberJson(response, endpoint);
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" });
9268
9275
  }
9269
9276
  function normalizeOpenChamberCollection(value) {
9270
9277
  if (Array.isArray(value)) return value;
@@ -9303,39 +9310,40 @@ function openChamberListIncludesBranch(list, directory, branch) {
9303
9310
  return !entryBranch || entryBranch === branch;
9304
9311
  });
9305
9312
  }
9306
- async function findOpenChamberProject({ baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9307
- const projects = await requestOpenChamberJson({ baseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
9313
+ async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9314
+ const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
9308
9315
  if (!Array.isArray(projects)) return null;
9309
9316
  const resolvedBase = path5.resolve(baseWorktree);
9310
9317
  return projects.find((project) => path5.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
9311
9318
  }
9312
- async function refreshOpenChamberProjectCopy({ baseUrl, baseWorktree, projectId, fetchImpl = globalThis.fetch } = {}) {
9319
+ async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
9313
9320
  if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
9314
- const response = await fetchImpl(openChamberUrl(baseUrl, `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, {}), { method: "POST" });
9315
- await readOpenChamberJson(response, "/experimental/project/{projectID}/copy/refresh");
9321
+ await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: `/experimental/project/${encodeURIComponent(projectId)}/copy/refresh`, method: "POST", fetchImpl });
9316
9322
  return { refreshed: true, projectId };
9317
9323
  }
9318
- async function listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9319
- return requestOpenChamberJson({ baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
9324
+ async function listOpenChamberGitWorktrees({ openchamberBaseUrl, baseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
9325
+ return requestOpenChamberJson({ baseUrl: openchamberBaseUrl || baseUrl, endpoint: "/api/git/worktrees", directory: baseWorktree, fetchImpl });
9320
9326
  }
9321
- async function verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9322
- const worktrees = await listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl });
9327
+ async function verifyOpenChamberGitWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9328
+ const worktrees = await listOpenChamberGitWorktrees({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, baseWorktree, fetchImpl });
9323
9329
  const verified = openChamberListIncludesBranch(worktrees, worktreePath, branch);
9324
9330
  if (!verified) {
9325
9331
  throw new Error(`OpenChamber Git worktree verification failed for ${worktreePath} on ${branch}.`);
9326
9332
  }
9327
9333
  return { worktree: true, branch: true };
9328
9334
  }
9329
- async function syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl = globalThis.fetch } = {}) {
9330
- const gitWorktree = await verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9331
- await requestOpenChamberJson({ baseUrl, endpoint: "/experimental/workspace/sync-list", method: "POST", directory: baseWorktree, fetchImpl });
9332
- const project = await findOpenChamberProject({ baseUrl, baseWorktree, fetchImpl });
9333
- if (!project?.id) throw new Error("OpenChamber project was not found after workspace sync; refusing to treat worktree as visible.");
9334
- await refreshOpenChamberProjectCopy({ baseUrl, baseWorktree, projectId: project.id, fetchImpl });
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 });
9335
9343
  const [worktrees, workspaces, directories] = await Promise.all([
9336
- listOpenChamberGitWorktrees({ baseUrl, baseWorktree, fetchImpl }),
9337
- requestOpenChamberJson({ baseUrl, endpoint: "/experimental/workspace", directory: baseWorktree, fetchImpl }),
9338
- requestOpenChamberJson({ baseUrl, endpoint: `/project/${encodeURIComponent(project.id)}/directories`, directory: baseWorktree, fetchImpl })
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 })
9339
9347
  ]);
9340
9348
  const visibility = {
9341
9349
  worktree: openChamberListIncludesBranch(worktrees, worktreePath, branch),
@@ -9345,7 +9353,7 @@ async function syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktr
9345
9353
  projectId: project.id
9346
9354
  };
9347
9355
  if (!visibility.worktree || !visibility.workspace || !visibility.projectDirectory) {
9348
- throw new Error(`OpenChamber visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
9356
+ throw new Error(`OpenCode visibility verification failed for ${worktreePath}: ${JSON.stringify(visibility)}`);
9349
9357
  }
9350
9358
  return visibility;
9351
9359
  }
@@ -9354,12 +9362,13 @@ function assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath } = {
9354
9362
  throw new Error(`OpenChamber worktree path is outside the configured ClickUp sibling scope: ${worktreePath}`);
9355
9363
  }
9356
9364
  }
9357
- async function createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9365
+ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists = false, fetchImpl = globalThis.fetch } = {}) {
9366
+ const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
9358
9367
  const worktreeName = clickUpOpenChamberWorktreeName(branch);
9359
9368
  const body = { name: worktreeName, mode: branchExists ? "existing" : "new", worktreeName, branchName: branch, startRef: branchExists ? branch : startPoint };
9360
9369
  let created;
9361
9370
  try {
9362
- created = await requestOpenChamberJson({ baseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
9371
+ created = await requestOpenChamberJson({ baseUrl: effectiveOpenChamberBaseUrl, endpoint: "/api/git/worktrees", method: "POST", directory: baseWorktree, body, fetchImpl });
9363
9372
  } catch (error) {
9364
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}`);
9365
9374
  }
@@ -9371,15 +9380,17 @@ async function createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch,
9371
9380
  if (createdBranch !== branch) {
9372
9381
  throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
9373
9382
  }
9374
- const verified = await verifyOpenChamberGitWorktree({ baseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
9383
+ const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
9375
9384
  return { created, worktree: path5.resolve(createdDirectory), branch: createdBranch, verified };
9376
9385
  }
9377
- async function registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9386
+ async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
9378
9387
  assertOpenChamberClickUpWorktreePath({ baseWorktree, worktreePath });
9379
- const visibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9388
+ const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9380
9389
  return { branch, worktree: path5.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
9381
9390
  }
9382
- async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, baseUrl = "", fetchImpl = globalThis.fetch, log = null } = {}) {
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;
9383
9394
  const effectiveParent = parentTaskId || taskId;
9384
9395
  const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
9385
9396
  const parentBranch = isSubtask ? deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : "";
@@ -9387,13 +9398,13 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9387
9398
  const prTarget = parentBranch || "dev";
9388
9399
  const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
9389
9400
  if (existing) {
9390
- const registered = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
9401
+ const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
9391
9402
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "metadata_reuse", visibility: registered.openChamber.visibility });
9392
9403
  return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9393
9404
  }
9394
9405
  const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9395
9406
  if (fs5.existsSync(worktreePath)) {
9396
- const registered = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
9407
+ const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
9397
9408
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
9398
9409
  return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9399
9410
  }
@@ -9401,40 +9412,54 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9401
9412
  if (isSubtask) {
9402
9413
  const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
9403
9414
  if (fs5.existsSync(parentWorktree)) {
9404
- const registeredParent = await registerOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
9415
+ const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
9405
9416
  parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
9406
9417
  } else {
9407
9418
  const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9408
9419
  const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
9409
- const createdParent = await createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, branchExists: parentBranchExists, fetchImpl });
9410
- const parentVisibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath: createdParent.worktree, branch: parentBranch, fetchImpl });
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 });
9411
9422
  parentBootstrap = { branch: parentBranch, worktree: createdParent.worktree, startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists, provider: "openchamber", visibility: parentVisibility };
9412
9423
  log?.({ type: "openchamber_worktree_created", taskId: effectiveParent, branch: parentBranch, worktree: parentBootstrap.worktree, startPoint: parentBootstrap.startPoint, visibility: parentVisibility });
9413
9424
  }
9414
9425
  }
9415
9426
  const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9416
9427
  const branchExists = clickUpGitRefExists(baseWorktree, branch, runGitFn);
9417
- const created = await createOpenChamberClickUpWorktree({ baseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists, fetchImpl });
9418
- const visibility = await syncOpenChamberWorktreeVisibility({ baseUrl, baseWorktree, worktreePath: created.worktree, branch, fetchImpl });
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 });
9419
9430
  log?.({ type: "openchamber_worktree_created", taskId, branch, worktree: created.worktree, startPoint: branchExists ? branch : startPoint, visibility });
9420
9431
  return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility } };
9421
9432
  }
9422
9433
  async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", opencodeBaseUrlConfigured = false, openchamberBaseUrl = "", openchamberBaseUrlConfigured = false, clickupClient = null, webhookWorktree = process.cwd(), fetchImpl = globalThis.fetch, ...options } = {}) {
9423
- void opencodeBaseUrl;
9424
9434
  void opencodeBaseUrlConfigured;
9425
9435
  if (!openchamberBaseUrlConfigured || !openchamberBaseUrl) return ensureClickUpTaskWorktree(options);
9426
9436
  try {
9427
- return await ensureClickUpTaskWorktreeOpenChamber({ ...options, baseUrl: openchamberBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
9437
+ return await ensureClickUpTaskWorktreeOpenChamber({ ...options, openchamberBaseUrl, opencodeBaseUrl, fetchImpl, log: (entry) => appendClickUpWebhookLocalLog(webhookWorktree, entry) });
9428
9438
  } catch (error) {
9429
9439
  const message = `OpenChamber worktree provisioning failed for ${options.taskId || "unknown task"}; raw git fallback is disabled to avoid invisible worktrees. ${error.message}`;
9430
9440
  appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_failed", taskId: options.taskId || null, message });
9431
9441
  if (typeof clickupClient?.postTaskComment === "function") {
9432
- try {
9433
- await clickupClient.postTaskComment({ taskId: options.taskId, comment: `${message}
9442
+ const taskId = options.taskId || "unknown task";
9443
+ const ledgerPath = clickUpCommentLedgerPath(webhookWorktree);
9444
+ const comment = `${message}
9434
9445
 
9435
- Optima did not run raw git worktree fallback. Please verify clickup.openchamber.base_url and OpenChamber Git API compatibility.` });
9436
- } catch (commentError) {
9437
- appendClickUpWebhookLocalLog(webhookWorktree, { type: "openchamber_worktree_blocker_comment_failed", taskId: options.taskId || null, message: commentError.message });
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 });
9438
9463
  }
9439
9464
  }
9440
9465
  throw new Error(message);
@@ -10234,23 +10259,29 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
10234
10259
  if (recent.includes(eventKey)) return { duplicate: true, state };
10235
10260
  return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
10236
10261
  }
10237
- function readClickUpCommentLedger(ledgerPath) {
10238
- const processed = /* @__PURE__ */ new Set();
10239
- if (!ledgerPath || !fs5.existsSync(ledgerPath)) return processed;
10262
+ function readClickUpCommentLedgerEntries(ledgerPath) {
10263
+ if (!ledgerPath || !fs5.existsSync(ledgerPath)) return [];
10240
10264
  try {
10241
10265
  const raw = fs5.readFileSync(ledgerPath, "utf8");
10266
+ const entries = [];
10242
10267
  for (const [index, line] of raw.split(/\r?\n/).entries()) {
10243
10268
  if (!line.trim()) continue;
10244
10269
  try {
10245
- const entry = JSON.parse(line);
10246
- if (entry?.key) processed.add(String(entry.key));
10270
+ entries.push(JSON.parse(line));
10247
10271
  } catch (error) {
10248
10272
  throw new Error(`malformed ledger row ${index + 1}: ${error.message}`);
10249
10273
  }
10250
10274
  }
10275
+ return entries;
10251
10276
  } catch (error) {
10252
10277
  throw new Error(`ClickUp comment ledger unavailable: ${error.message}`);
10253
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
+ }
10254
10285
  return processed;
10255
10286
  }
10256
10287
  function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
@@ -10316,6 +10347,35 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
10316
10347
  recordedAt: at.toISOString()
10317
10348
  });
10318
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
+ }
10319
10379
  function clickUpTaskIdFromPayload(payload = {}) {
10320
10380
  return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
10321
10381
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"