@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.
- package/assets/agents/workflow_product_manager.md +1 -1
- package/dist/index.js +110 -50
- package/dist/sanitize_cli.js +110 -50
- package/package.json +1 -1
|
@@ -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.
|
|
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(
|
|
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(
|
|
9250
|
+
throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
|
|
9250
9251
|
}
|
|
9251
9252
|
return data;
|
|
9252
9253
|
}
|
|
9253
|
-
async function
|
|
9254
|
-
if (typeof fetchImpl !== "function") throw new Error(
|
|
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({
|
|
9300
|
-
const projects = await
|
|
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({
|
|
9312
|
+
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9306
9313
|
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
9307
|
-
|
|
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
|
|
9324
|
-
|
|
9325
|
-
const
|
|
9326
|
-
|
|
9327
|
-
await
|
|
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({
|
|
9330
|
-
|
|
9331
|
-
|
|
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(`
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
9403
|
-
const parentVisibility = await syncOpenChamberWorktreeVisibility({
|
|
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({
|
|
9411
|
-
const visibility = await syncOpenChamberWorktreeVisibility({
|
|
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,
|
|
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
|
-
|
|
9426
|
-
|
|
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
|
|
9429
|
-
|
|
9430
|
-
|
|
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
|
|
10231
|
-
|
|
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
|
-
|
|
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
|
}
|
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;
|
|
@@ -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(
|
|
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(
|
|
9257
|
+
throw new Error(`${serviceName} ${endpoint} failed: ${response.status} ${message}`);
|
|
9257
9258
|
}
|
|
9258
9259
|
return data;
|
|
9259
9260
|
}
|
|
9260
|
-
async function
|
|
9261
|
-
if (typeof fetchImpl !== "function") throw new Error(
|
|
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({
|
|
9307
|
-
const projects = await
|
|
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({
|
|
9319
|
+
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9313
9320
|
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
9314
|
-
|
|
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
|
|
9331
|
-
|
|
9332
|
-
const
|
|
9333
|
-
|
|
9334
|
-
await
|
|
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({
|
|
9337
|
-
|
|
9338
|
-
|
|
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(`
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
9410
|
-
const parentVisibility = await syncOpenChamberWorktreeVisibility({
|
|
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({
|
|
9418
|
-
const visibility = await syncOpenChamberWorktreeVisibility({
|
|
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,
|
|
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
|
-
|
|
9433
|
-
|
|
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
|
|
9436
|
-
|
|
9437
|
-
|
|
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
|
|
10238
|
-
|
|
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
|
-
|
|
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
|
}
|