@defend-tech/opencode-optima 0.1.67 → 0.1.68
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/Agents_Common.md +3 -1
- package/Agents_Common.prompt.md +3 -1
- package/assets/agents/workflow_product_manager.md +6 -4
- package/dist/index.js +165 -21
- package/dist/sanitize_cli.js +165 -21
- package/package.json +1 -1
package/Agents_Common.md
CHANGED
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
- Use the ClickUp skill and ClickUp MCP/tools for all ClickUp reads, writes, comments, field updates, status transitions, assignments, and dashboard operations; if unavailable or forbidden, state the sync blocker and leave a manual-sync payload in task/evidence.
|
|
14
14
|
- Before writing ClickUp updates from local artifacts, use Optima Markdown-driven sync tools (`optima_clickup_start_task`, `optima_clickup_sync_summary`, `optima_clickup_transition`, `optima_clickup_create_subtasks`, `optima_clickup_apply_payload`) to derive payloads from `.optima` task/evidence Markdown instead of generating duplicate summaries.
|
|
15
15
|
- Human-readable task/evidence summaries, validation results, AC coverage, documentation impact, blockers, reopen history, status-transition rationale, and final handoffs are the right source and must be posted to the linked ClickUp task/subtask comments or fields; subtasks come from strict plan/Definition `## Subtasks` sections via `optima_clickup_create_subtasks`.
|
|
16
|
+
- ClickUp comments are human-facing model updates only. Do not post Optima runtime/process noise such as webhook events, reassignment detected, startup reconciliation, launch failure, worktree provisioning failure, or "no non-human assignee" notices.
|
|
17
|
+
- ClickUp comments must use real Markdown line breaks. Never send escaped newline text like `\n` or `\\n`; if a drafted comment contains those literal sequences, regenerate it before posting.
|
|
16
18
|
- WPM rewrites the ClickUp task description on initial pickup and again at plan completion with the complete current/final description of what must be done, distinct from comments.
|
|
17
19
|
- Keep raw logs in evidence storage; do not paste raw logs wholesale into ClickUp. Post concise summaries, paths/links, or relevant excerpts only.
|
|
18
20
|
- `product_manager` may investigate, answer, pre-estimate "a qué huele" small/medium/large plus rough story points, and operate ClickUp dashboards; development requests must be converted into properly routed ClickUp tasks.
|
|
19
21
|
- ClickUp delivery types are `Tarea`, `Bug`, `Doc`, `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, and `Respuesta del formulario` unless converted or linked.
|
|
20
22
|
- WPM estimates `Story Points` during `plan` and re-estimates on material plan changes.
|
|
21
23
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if a task worktree lacks that file, use Optima-provided fallback role context and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role identifiers in workflow text and code instead of personal names.
|
|
22
|
-
- ClickUp status actions: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments,
|
|
24
|
+
- ClickUp status actions: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Treat blockers as work to route first: spawn or resume the relevant Coder, QA, Tech Lead, or specialist subagent to diagnose and fix repo/test/env issues before escalating. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, true external `in progress` blockers from missing credentials/permissions/tools/access after attempted local resolution, or parent `validation` with a functional preview URL; never for generic handoff, cleanup, subtasks, partial-phase stops, local dependency/test failures that a subagent can fix, or "no non-human assignee" fallback. Validator/QA may merge validated subtasks into the parent branch without CTO/PO approval; parent human `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev receives the code. `completed`/`Closed` ignore unless reopened.
|
|
23
25
|
- One shared-worktree `implementation` task may be active; ClickUp-first delivery should use task-specific worktrees/branches.
|
|
24
26
|
- Agent messages must start with `[Agent Message] From: <agent_name> To: <agent_name>`.
|
|
25
27
|
- Clarifications, blockers, dependencies, and reviews go through PMA.
|
package/Agents_Common.prompt.md
CHANGED
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
- Use the ClickUp skill and ClickUp MCP/tools for all ClickUp reads, writes, comments, field updates, status transitions, assignments, and dashboard operations; if unavailable or forbidden, state the sync blocker and leave a manual-sync payload in task/evidence.
|
|
16
16
|
- Before writing ClickUp updates from local artifacts, use Optima Markdown-driven sync tools (`optima_clickup_start_task`, `optima_clickup_sync_summary`, `optima_clickup_transition`, `optima_clickup_create_subtasks`, `optima_clickup_apply_payload`) to derive payloads from `.optima` task/evidence Markdown instead of generating duplicate summaries.
|
|
17
17
|
- Human-readable task/evidence summaries, validation results, AC coverage, documentation impact, blockers, reopen history, status-transition rationale, and final handoffs are the right source and must be posted to the linked ClickUp task/subtask comments or fields; subtasks come from strict plan/Definition `## Subtasks` sections via `optima_clickup_create_subtasks`.
|
|
18
|
+
- ClickUp comments are human-facing model updates only. Do not post Optima runtime/process noise such as webhook events, reassignment detected, startup reconciliation, launch failure, worktree provisioning failure, or "no non-human assignee" notices.
|
|
19
|
+
- ClickUp comments must use real Markdown line breaks. Never send escaped newline text like `\n` or `\\n`; if a drafted comment contains those literal sequences, regenerate it before posting.
|
|
18
20
|
- WPM rewrites the ClickUp task description on initial pickup and again at plan completion with the complete current/final description of what must be done, distinct from comments.
|
|
19
21
|
- Keep raw logs in evidence storage; do not paste raw logs wholesale into ClickUp. Post concise summaries, paths/links, or relevant excerpts only.
|
|
20
22
|
- `product_manager` may investigate, answer, pre-estimate "a qué huele" small/medium/large plus rough story points, and operate ClickUp dashboards; development requests must become routed ClickUp tasks.
|
|
21
23
|
- ClickUp-first delivery types: `Tarea`, `Bug`, `Doc`, `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario` unless converted or linked.
|
|
22
24
|
- WPM estimates `Story Points` during `plan` and re-estimates on material plan changes.
|
|
23
25
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if missing in a task worktree, use Optima fallback role context/configured ClickUp IDs instead of blocking solely on the missing file.
|
|
24
|
-
- ClickUp-first statuses: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments,
|
|
26
|
+
- ClickUp-first statuses: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Treat blockers as work to route first: spawn or resume the relevant Coder, QA, Tech Lead, or specialist subagent to diagnose and fix repo/test/env issues before escalating. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, true external `in progress` blockers from missing credentials/permissions/tools/access after attempted local resolution, or parent `validation` with a functional preview URL; never for generic handoff, cleanup, subtasks, partial-phase stops, local dependency/test failures that a subagent can fix, or "no non-human assignee" fallback. Validator/QA may merge validated subtasks into the parent branch without CTO/PO approval; parent human `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev receives the code. `completed`/`Closed` ignore unless reopened.
|
|
25
27
|
- Signed agent-to-agent messages must start exactly: `[Agent Message] From: <agent_name> To: <agent_name>`.
|
|
26
28
|
- Direct all clarifications, blockers, and specialist questions through PMA unless explicitly in a direct discussion-capable role.
|
|
27
29
|
- Read relevant docs/tasks fully when they govern the current work. Prefer targeted CodeMap navigation before broad source search.
|
|
@@ -15,8 +15,9 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
15
15
|
- `workflow_product_manager` owns delivery ops: ClickUp status, routing/handoffs, decomposition, validation gates, Git worktree/branch/PR flow, evidence, and closure.
|
|
16
16
|
- `product_manager` remains compatibility/product/planning PMA for requirements, SCRs, product truth, rough pre-estimation, and default/legacy orchestration when ClickUp-first is not opted in. Do not remove, shadow, or break `product_manager`.
|
|
17
17
|
- ClickUp Docs/tasks are source of truth for intent, state, comments, assignment, validation, and closure. Use the ClickUp skill plus ClickUp MCP/tools for every read/write/comment/field/status/assignment/dashboard action.
|
|
18
|
-
- RULE NUMBER ONE: your operating objective is
|
|
19
|
-
- OpenCode session output is not visible to humans unless you post it to ClickUp.
|
|
18
|
+
- RULE NUMBER ONE: your operating objective is delivered work, not merely zero PM-assigned tasks. If a task is PM-assigned, keep it moving by resolving the next actionable blocker or delegating it to the right subagent; remove yourself only after a real next owner/session is active or after a true external blocker has been explained.
|
|
19
|
+
- OpenCode session output is not visible to humans unless you post it to ClickUp. Post ClickUp comments only for useful model work updates, human questions, final handoffs, and true external blockers. Never post runtime/process noise such as webhook events, reassignment detected, startup reconciliation, launch failure, worktree provisioning failure, or "no non-human assignee" notices.
|
|
20
|
+
- ClickUp comments must be readable Markdown with real line breaks. Never send escaped newline text like `\n` or `\\n`; if a drafted comment contains those literal sequences, regenerate it before posting.
|
|
20
21
|
- Keep raw logs in evidence; ClickUp gets summaries, paths/links, or excerpts only.
|
|
21
22
|
|
|
22
23
|
## Definition, Mirrors, Sessions
|
|
@@ -43,7 +44,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
43
44
|
- Human approval allowlist: never assign `CTO`/`PO` except for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/permissions/tools/access, or parent `validation` with a functional preview URL such as `https://<taskid>-preview.defend.tech`. Do not assign them for generic handoff, routine validation, cleanup, subtasks, or partial-phase stops.
|
|
44
45
|
- `backlog`: ignore until prioritized.
|
|
45
46
|
- `plan`: clarify AC/SCR/test strategy with Validator/QA; decompose; create/update Definition; estimate Story Points; remove PM assignee first; assign the next delivery owner. Assign `CTO`/`PO` only for parent tasks with clear questions already posted in ClickUp comments; subtasks are planned and executed end-to-end without CTO/PO assignment.
|
|
46
|
-
- `in progress`: execute through the assigned delivery agent or workflow runner. Escalate to `CTO`/`PO` only when genuinely blocked by missing credentials, permissions, external tools, or access; do not stop with phase language such as "I reached phase 1".
|
|
47
|
+
- `in progress`: execute through the assigned delivery agent or workflow runner. Treat blockers as work to solve first: spawn or resume Coder for code/build/dependency failures, QA for validation/test/evidence failures, Tech Lead for architecture/review/merge failures, and the relevant specialist for domain blockers. Escalate to `CTO`/`PO` only when genuinely blocked by missing credentials, permissions, external tools, or access after local/subagent resolution attempts; do not stop with phase language such as "I reached phase 1" or "no non-human assignee is available".
|
|
47
48
|
- `validation`: route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks. Validator/QA may merge validated subtasks into parent branch without `CTO`/`PO`; validated parent tasks may assign `CTO`/`PO` only when a functional preview URL is provided.
|
|
48
49
|
- `merge`: parent-only post-approval automation after a human comments `Approved`; remove human assignees, assign yourself or the merge owner, merge parent PR into `dev`, clean workspaces/worktrees/branches, push to `dev`, and ensure the dev environment contains the code. Conflicts or merge failures return the affected item to `in progress`.
|
|
49
50
|
- `completed` / `Closed`: no execution unless explicitly reopened.
|
|
@@ -66,7 +67,8 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
66
67
|
|
|
67
68
|
- Orchestrate; do not silently implement specialist work yourself.
|
|
68
69
|
- Delegate through ClickUp task/subtask assignment or task-specific specialist sessions with ClickUp context, AC, evidence, sync duties, branch target, Story Points, Definition link, final Documentation needs, and validation requirements.
|
|
69
|
-
- Never abandon a PM-assigned task: keep working until unblocked and handed off, or
|
|
70
|
+
- Never abandon a PM-assigned task: keep working until unblocked and handed off. If blocked, first create or resume the appropriate subagent and ask it to remove the blocker; only escalate true external blockers after documenting exactly what was tried and what external input is required.
|
|
71
|
+
- When a human comments or mentions `@Defend Tech Product Manager`, answer directly in the task with the requested clarification and then route the next action. Do not remove yourself merely because the human asks what is needed.
|
|
70
72
|
- On pickup, rewrite the ClickUp task description with the complete current description of what must be done; do not rely on status comments.
|
|
71
73
|
- At plan completion, rewrite the ClickUp task description again with the complete final plan/Definition, distinct from the plan comment.
|
|
72
74
|
- Estimate Story Points during `plan`, write them to ClickUp `Story Points`, and re-estimate when material plan changes alter scope/risk.
|
package/dist/index.js
CHANGED
|
@@ -8527,6 +8527,8 @@ 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_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS = 6 * 60 * 60 * 1e3;
|
|
8531
|
+
var CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8530
8532
|
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8531
8533
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8532
8534
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
@@ -9807,6 +9809,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9807
9809
|
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
9808
9810
|
opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
|
|
9809
9811
|
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9812
|
+
),
|
|
9813
|
+
assignmentWatchdogIntervalMs: normalizeNonNegativeInteger(
|
|
9814
|
+
opencode.assignment_watchdog_interval_ms ?? opencode.assignmentWatchdogIntervalMs ?? raw.assignment_watchdog_interval_ms ?? raw.assignmentWatchdogIntervalMs,
|
|
9815
|
+
CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS
|
|
9810
9816
|
)
|
|
9811
9817
|
},
|
|
9812
9818
|
openchamber: {
|
|
@@ -10772,17 +10778,87 @@ function openCodeResultSummary(result) {
|
|
|
10772
10778
|
status: result?.status || result?.response?.status || null
|
|
10773
10779
|
};
|
|
10774
10780
|
}
|
|
10781
|
+
function openCodeMessagePayload(message = {}) {
|
|
10782
|
+
if (!isPlainObject(message)) return {};
|
|
10783
|
+
const rawData = message.data;
|
|
10784
|
+
if (isPlainObject(rawData)) {
|
|
10785
|
+
return {
|
|
10786
|
+
...rawData,
|
|
10787
|
+
id: rawData.id || message.id,
|
|
10788
|
+
time_created: rawData.time_created ?? message.time_created,
|
|
10789
|
+
time_updated: rawData.time_updated ?? message.time_updated
|
|
10790
|
+
};
|
|
10791
|
+
}
|
|
10792
|
+
if (typeof rawData === "string" && rawData.trim()) {
|
|
10793
|
+
try {
|
|
10794
|
+
const parsed = JSON.parse(rawData);
|
|
10795
|
+
if (isPlainObject(parsed)) {
|
|
10796
|
+
return {
|
|
10797
|
+
...parsed,
|
|
10798
|
+
id: parsed.id || message.id,
|
|
10799
|
+
time_created: parsed.time_created ?? message.time_created,
|
|
10800
|
+
time_updated: parsed.time_updated ?? message.time_updated
|
|
10801
|
+
};
|
|
10802
|
+
}
|
|
10803
|
+
} catch {
|
|
10804
|
+
}
|
|
10805
|
+
}
|
|
10806
|
+
return message;
|
|
10807
|
+
}
|
|
10775
10808
|
function normalizeOpenCodeMessageRole(message = {}) {
|
|
10776
|
-
|
|
10809
|
+
const payload = openCodeMessagePayload(message);
|
|
10810
|
+
return payload.role || payload.info?.role || payload.author?.role || payload.type || "";
|
|
10777
10811
|
}
|
|
10778
10812
|
function normalizeOpenCodeMessageAgent(message = {}) {
|
|
10779
|
-
|
|
10813
|
+
const payload = openCodeMessagePayload(message);
|
|
10814
|
+
return payload.agent || payload.info?.agent || payload.mode?.agent || payload.author?.agent || "";
|
|
10780
10815
|
}
|
|
10781
10816
|
function normalizeOpenCodeMessageMode(message = {}) {
|
|
10782
|
-
|
|
10817
|
+
const payload = openCodeMessagePayload(message);
|
|
10818
|
+
return payload.mode || payload.info?.mode || payload.metadata?.mode || "";
|
|
10783
10819
|
}
|
|
10784
10820
|
function normalizeOpenCodeMessageId(message = {}) {
|
|
10785
|
-
|
|
10821
|
+
const payload = openCodeMessagePayload(message);
|
|
10822
|
+
return payload.id || payload.messageID || payload.messageId || payload.info?.id || payload.data?.id || message.id || null;
|
|
10823
|
+
}
|
|
10824
|
+
function openCodeMessageTimestampMs(message = {}, key = "updated") {
|
|
10825
|
+
const payload = openCodeMessagePayload(message);
|
|
10826
|
+
const time = payload.time || payload.info?.time || {};
|
|
10827
|
+
const value = key === "completed" ? payload.time_completed ?? payload.timeCompleted ?? time.completed : key === "created" ? payload.time_created ?? payload.timeCreated ?? time.created : payload.time_updated ?? payload.timeUpdated ?? time.updated ?? payload.time_created ?? payload.timeCreated ?? time.created;
|
|
10828
|
+
const number = Number(value);
|
|
10829
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10830
|
+
}
|
|
10831
|
+
function isOpenCodeAssistantMessageRunning(message = {}) {
|
|
10832
|
+
if (normalizeLooseToken(normalizeOpenCodeMessageRole(message)) !== "assistant") return false;
|
|
10833
|
+
const started = openCodeMessageTimestampMs(message, "created") > 0;
|
|
10834
|
+
if (!started) return false;
|
|
10835
|
+
return openCodeMessageTimestampMs(message, "completed") === 0;
|
|
10836
|
+
}
|
|
10837
|
+
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10 } = {}) {
|
|
10838
|
+
const messages = await readOpenCodeSessionMessages(client, { sessionId, directory, limit });
|
|
10839
|
+
if (!messages) return { ok: false, reason: "message_inspection_unavailable", sessionId, directory };
|
|
10840
|
+
const enriched = messages.map((message, index) => ({
|
|
10841
|
+
message,
|
|
10842
|
+
index,
|
|
10843
|
+
sortTime: openCodeMessageTimestampMs(message, "updated") || index,
|
|
10844
|
+
createdAt: openCodeMessageTimestampMs(message, "created"),
|
|
10845
|
+
completedAt: openCodeMessageTimestampMs(message, "completed")
|
|
10846
|
+
})).sort((a, b) => a.sortTime - b.sortTime || a.index - b.index);
|
|
10847
|
+
const assistantMessages = enriched.filter((entry) => normalizeLooseToken(normalizeOpenCodeMessageRole(entry.message)) === "assistant");
|
|
10848
|
+
const latestAssistant = assistantMessages.at(-1) || null;
|
|
10849
|
+
const latest = enriched.at(-1) || null;
|
|
10850
|
+
const running = latestAssistant ? isOpenCodeAssistantMessageRunning(latestAssistant.message) : false;
|
|
10851
|
+
return {
|
|
10852
|
+
ok: true,
|
|
10853
|
+
sessionId,
|
|
10854
|
+
directory,
|
|
10855
|
+
count: messages.length,
|
|
10856
|
+
running,
|
|
10857
|
+
latestMessageId: latest ? normalizeOpenCodeMessageId(latest.message) : null,
|
|
10858
|
+
latestAssistantMessageId: latestAssistant ? normalizeOpenCodeMessageId(latestAssistant.message) : null,
|
|
10859
|
+
latestAssistantCreatedAt: latestAssistant?.createdAt || 0,
|
|
10860
|
+
latestAssistantCompletedAt: latestAssistant?.completedAt || 0
|
|
10861
|
+
};
|
|
10786
10862
|
}
|
|
10787
10863
|
function summarizeOpenCodeMessages(messages = [], { snippetLength = 160, maxMessages = 50 } = {}) {
|
|
10788
10864
|
const list = Array.isArray(messages) ? messages.slice(0, maxMessages) : [];
|
|
@@ -10922,8 +10998,9 @@ async function probeOpenCodeSessionControl(client, { directory, agent, omitAgent
|
|
|
10922
10998
|
};
|
|
10923
10999
|
}
|
|
10924
11000
|
function openCodeMessageText(message) {
|
|
10925
|
-
const
|
|
10926
|
-
const
|
|
11001
|
+
const payload = openCodeMessagePayload(message);
|
|
11002
|
+
const parts = [payload?.text, payload?.content, payload?.message, payload?.body?.text, payload?.data?.text];
|
|
11003
|
+
const partList = Array.isArray(payload?.parts) ? payload.parts : Array.isArray(payload?.body?.parts) ? payload.body.parts : [];
|
|
10927
11004
|
for (const part of partList) parts.push(part?.text, part?.content);
|
|
10928
11005
|
return parts.filter((value) => typeof value === "string").join("\n");
|
|
10929
11006
|
}
|
|
@@ -11141,6 +11218,7 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
|
|
|
11141
11218
|
entry.cleaning = true;
|
|
11142
11219
|
const cleanup = (async () => {
|
|
11143
11220
|
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
|
|
11221
|
+
if (entry.assignmentWatchdog?.interval) clearInterval(entry.assignmentWatchdog.interval);
|
|
11144
11222
|
const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
|
|
11145
11223
|
if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
|
|
11146
11224
|
const preservedResult = { ok: true, skipped: true, reason: "remote_webhook_preserved" };
|
|
@@ -11154,10 +11232,10 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
|
|
|
11154
11232
|
if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
|
|
11155
11233
|
return result;
|
|
11156
11234
|
}
|
|
11157
|
-
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
11235
|
+
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners, assignmentWatchdog = null } = {}) {
|
|
11158
11236
|
if (!isClickUpWebhookStateActive(state, config)) return null;
|
|
11159
11237
|
const key = managedClickUpWebhookKey({ worktree, state, config });
|
|
11160
|
-
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11238
|
+
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, assignmentWatchdog, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11161
11239
|
activeClickUpWebhookLifecycleRegistry.set(key, entry);
|
|
11162
11240
|
installClickUpWebhookSignalHandlers();
|
|
11163
11241
|
clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
|
|
@@ -11447,19 +11525,54 @@ function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = p
|
|
|
11447
11525
|
timer?.unref?.();
|
|
11448
11526
|
return { scheduled: true, delayMs: normalizedDelayMs, timer };
|
|
11449
11527
|
}
|
|
11450
|
-
|
|
11451
|
-
|
|
11528
|
+
function scheduleClickUpAssignmentWatchdog({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = setInterval, intervalMs = config?.opencode?.assignmentWatchdogIntervalMs ?? CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
|
|
11529
|
+
const normalizedIntervalMs = normalizeNonNegativeInteger(intervalMs, CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS);
|
|
11530
|
+
if (!normalizedIntervalMs) {
|
|
11531
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_disabled", webhookId: state?.webhookId || null });
|
|
11532
|
+
return { scheduled: false, intervalMs: 0, reason: "disabled" };
|
|
11533
|
+
}
|
|
11534
|
+
let running = false;
|
|
11535
|
+
const run = () => {
|
|
11536
|
+
if (running) {
|
|
11537
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_tick_skipped", reason: "previous_tick_running" });
|
|
11538
|
+
return;
|
|
11539
|
+
}
|
|
11540
|
+
running = true;
|
|
11541
|
+
const latestState = readClickUpWebhookState(worktree, config);
|
|
11542
|
+
Promise.resolve().then(() => reconcile({
|
|
11543
|
+
config,
|
|
11544
|
+
state: latestState?.webhookId ? latestState : state,
|
|
11545
|
+
worktree,
|
|
11546
|
+
clickupClient,
|
|
11547
|
+
openCodeClient,
|
|
11548
|
+
saveState,
|
|
11549
|
+
assignmentMode: "watchdog"
|
|
11550
|
+
})).catch((error) => {
|
|
11551
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_failed", message: error.message });
|
|
11552
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_failed", message: error.message });
|
|
11553
|
+
}).finally(() => {
|
|
11554
|
+
running = false;
|
|
11555
|
+
});
|
|
11556
|
+
};
|
|
11557
|
+
const interval = scheduler(run, normalizedIntervalMs);
|
|
11558
|
+
interval?.unref?.();
|
|
11559
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_scheduled", intervalMs: normalizedIntervalMs, webhookId: state?.webhookId || null });
|
|
11560
|
+
return { scheduled: true, intervalMs: normalizedIntervalMs, interval };
|
|
11561
|
+
}
|
|
11562
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT, assignmentMode = "startup" } = {}) {
|
|
11563
|
+
const watchdogMode = assignmentMode === "watchdog";
|
|
11564
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_started" : "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
11452
11565
|
if (!config || !clickupClient?.listAssignedTasks) {
|
|
11453
11566
|
const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
11454
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
|
|
11567
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...skipped });
|
|
11455
11568
|
return skipped;
|
|
11456
11569
|
}
|
|
11457
11570
|
const readiness = await waitForReadiness(openCodeClient, { worktree, now, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
11458
11571
|
appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
|
|
11459
11572
|
if (!readiness.ok) {
|
|
11460
11573
|
const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
|
|
11461
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
11462
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
|
|
11574
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_failed" : "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
11575
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...failed });
|
|
11463
11576
|
return failed;
|
|
11464
11577
|
}
|
|
11465
11578
|
let authorizedUserId = "";
|
|
@@ -11477,6 +11590,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11477
11590
|
if (saveState) saveState(nextState);
|
|
11478
11591
|
};
|
|
11479
11592
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
11593
|
+
const startupCommentStartMs = lastWebhookMs || Math.max(0, now().getTime() - CLICKUP_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS);
|
|
11480
11594
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
11481
11595
|
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
11482
11596
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
@@ -11495,9 +11609,29 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11495
11609
|
routed.ignored += 1;
|
|
11496
11610
|
continue;
|
|
11497
11611
|
}
|
|
11612
|
+
if (watchdogMode) {
|
|
11613
|
+
const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
|
|
11614
|
+
const metadata = normalizeAgentMetadataJson(existingMetadata);
|
|
11615
|
+
const rawSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey);
|
|
11616
|
+
const sessionId = typeof rawSessionId === "string" && rawSessionId.startsWith("ses_") ? rawSessionId : "";
|
|
11617
|
+
const directory = String(getNestedMetadataValue(metadata, "task.worktree") || "").trim();
|
|
11618
|
+
if (sessionId) {
|
|
11619
|
+
try {
|
|
11620
|
+
const activity = await inspectOpenCodeSessionActivity(openCodeClient, { sessionId, directory });
|
|
11621
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity", taskId, sessionId, directory: directory || null, ...activity });
|
|
11622
|
+
if (activity.running) {
|
|
11623
|
+
routed.ignored += 1;
|
|
11624
|
+
routed.tasks.push({ taskId, action: "ignored", ok: true, sessionId, branch: getNestedMetadataValue(metadata, "task.branch") || null, worktree: directory || null, verification: "session_running", delivered: false });
|
|
11625
|
+
continue;
|
|
11626
|
+
}
|
|
11627
|
+
} catch (error) {
|
|
11628
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity_failed", taskId, sessionId, directory: directory || null, message: error.message });
|
|
11629
|
+
}
|
|
11630
|
+
}
|
|
11631
|
+
}
|
|
11498
11632
|
try {
|
|
11499
11633
|
const result = await routeClickUpWebhookEvent({
|
|
11500
|
-
payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true },
|
|
11634
|
+
payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true, assignment_watchdog: watchdogMode },
|
|
11501
11635
|
config,
|
|
11502
11636
|
state: mutableState,
|
|
11503
11637
|
worktree,
|
|
@@ -11547,12 +11681,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11547
11681
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: "error", sessionId: null, reason: "startup_reconciliation_task_failed" });
|
|
11548
11682
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
|
|
11549
11683
|
}
|
|
11550
|
-
if (!
|
|
11684
|
+
if (!startupCommentStartMs || !clickupClient?.getTaskComments) continue;
|
|
11551
11685
|
try {
|
|
11552
|
-
const commentsResponse = await clickupClient.getTaskComments({ taskId, start:
|
|
11686
|
+
const commentsResponse = await clickupClient.getTaskComments({ taskId, start: startupCommentStartMs, limit: CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT });
|
|
11553
11687
|
for (const comment of clickUpCommentListItems(commentsResponse).slice(0, CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT)) {
|
|
11554
11688
|
const commentMs = clickUpCommentUpdatedMs(comment);
|
|
11555
|
-
if (!commentMs || commentMs <=
|
|
11689
|
+
if (!commentMs || commentMs <= startupCommentStartMs) continue;
|
|
11556
11690
|
const authorId = clickUpCommentAuthorId(comment);
|
|
11557
11691
|
if (authorId && (authorId === config.routing.ignoredCommentAuthorId || authorId === authorizedUserId)) continue;
|
|
11558
11692
|
if (!clickUpCommentMentionsProductManager(comment, config.routing)) continue;
|
|
@@ -11579,11 +11713,11 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11579
11713
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
|
|
11580
11714
|
}
|
|
11581
11715
|
}
|
|
11582
|
-
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId);
|
|
11716
|
+
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId && task.verification !== "session_running");
|
|
11583
11717
|
if (emptyPromptSessions.length > 0) {
|
|
11584
11718
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_validation_failed", tasks: emptyPromptSessions });
|
|
11585
11719
|
}
|
|
11586
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
|
|
11720
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...routed });
|
|
11587
11721
|
return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
|
|
11588
11722
|
}
|
|
11589
11723
|
function clickUpWebhookExpectedPath(config) {
|
|
@@ -12897,7 +13031,17 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
12897
13031
|
...listenerState,
|
|
12898
13032
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12899
13033
|
}, clickUpWebhookValidation.config);
|
|
12900
|
-
|
|
13034
|
+
const assignmentWatchdog = scheduleClickUpAssignmentWatchdog({
|
|
13035
|
+
config: clickUpWebhookValidation.config,
|
|
13036
|
+
state: activeState,
|
|
13037
|
+
worktree,
|
|
13038
|
+
clickupClient: lifecycleClickUpClient,
|
|
13039
|
+
openCodeClient: input.client,
|
|
13040
|
+
scheduler: input.assignmentWatchdogScheduler,
|
|
13041
|
+
intervalMs: input.assignmentWatchdogIntervalMs,
|
|
13042
|
+
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
|
|
13043
|
+
});
|
|
13044
|
+
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry, assignmentWatchdog });
|
|
12901
13045
|
scheduleClickUpStartupReconciliation({
|
|
12902
13046
|
config: clickUpWebhookValidation.config,
|
|
12903
13047
|
state: activeState,
|
|
@@ -13553,7 +13697,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13553
13697
|
}
|
|
13554
13698
|
};
|
|
13555
13699
|
}
|
|
13556
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13700
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, inspectOpenCodeSessionActivity, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpAssignmentWatchdog, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13557
13701
|
export {
|
|
13558
13702
|
OptimaPlugin as default
|
|
13559
13703
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8534,6 +8534,8 @@ 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_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS = 6 * 60 * 60 * 1e3;
|
|
8538
|
+
var CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8537
8539
|
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8538
8540
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8539
8541
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
@@ -9814,6 +9816,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9814
9816
|
startupReconciliationDelayMs: normalizeNonNegativeInteger(
|
|
9815
9817
|
opencode.startup_reconciliation_delay_ms ?? opencode.startupReconciliationDelayMs ?? raw.startup_reconciliation_delay_ms ?? raw.startupReconciliationDelayMs,
|
|
9816
9818
|
CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS
|
|
9819
|
+
),
|
|
9820
|
+
assignmentWatchdogIntervalMs: normalizeNonNegativeInteger(
|
|
9821
|
+
opencode.assignment_watchdog_interval_ms ?? opencode.assignmentWatchdogIntervalMs ?? raw.assignment_watchdog_interval_ms ?? raw.assignmentWatchdogIntervalMs,
|
|
9822
|
+
CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS
|
|
9817
9823
|
)
|
|
9818
9824
|
},
|
|
9819
9825
|
openchamber: {
|
|
@@ -10779,17 +10785,87 @@ function openCodeResultSummary(result) {
|
|
|
10779
10785
|
status: result?.status || result?.response?.status || null
|
|
10780
10786
|
};
|
|
10781
10787
|
}
|
|
10788
|
+
function openCodeMessagePayload(message = {}) {
|
|
10789
|
+
if (!isPlainObject(message)) return {};
|
|
10790
|
+
const rawData = message.data;
|
|
10791
|
+
if (isPlainObject(rawData)) {
|
|
10792
|
+
return {
|
|
10793
|
+
...rawData,
|
|
10794
|
+
id: rawData.id || message.id,
|
|
10795
|
+
time_created: rawData.time_created ?? message.time_created,
|
|
10796
|
+
time_updated: rawData.time_updated ?? message.time_updated
|
|
10797
|
+
};
|
|
10798
|
+
}
|
|
10799
|
+
if (typeof rawData === "string" && rawData.trim()) {
|
|
10800
|
+
try {
|
|
10801
|
+
const parsed = JSON.parse(rawData);
|
|
10802
|
+
if (isPlainObject(parsed)) {
|
|
10803
|
+
return {
|
|
10804
|
+
...parsed,
|
|
10805
|
+
id: parsed.id || message.id,
|
|
10806
|
+
time_created: parsed.time_created ?? message.time_created,
|
|
10807
|
+
time_updated: parsed.time_updated ?? message.time_updated
|
|
10808
|
+
};
|
|
10809
|
+
}
|
|
10810
|
+
} catch {
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
return message;
|
|
10814
|
+
}
|
|
10782
10815
|
function normalizeOpenCodeMessageRole(message = {}) {
|
|
10783
|
-
|
|
10816
|
+
const payload = openCodeMessagePayload(message);
|
|
10817
|
+
return payload.role || payload.info?.role || payload.author?.role || payload.type || "";
|
|
10784
10818
|
}
|
|
10785
10819
|
function normalizeOpenCodeMessageAgent(message = {}) {
|
|
10786
|
-
|
|
10820
|
+
const payload = openCodeMessagePayload(message);
|
|
10821
|
+
return payload.agent || payload.info?.agent || payload.mode?.agent || payload.author?.agent || "";
|
|
10787
10822
|
}
|
|
10788
10823
|
function normalizeOpenCodeMessageMode(message = {}) {
|
|
10789
|
-
|
|
10824
|
+
const payload = openCodeMessagePayload(message);
|
|
10825
|
+
return payload.mode || payload.info?.mode || payload.metadata?.mode || "";
|
|
10790
10826
|
}
|
|
10791
10827
|
function normalizeOpenCodeMessageId(message = {}) {
|
|
10792
|
-
|
|
10828
|
+
const payload = openCodeMessagePayload(message);
|
|
10829
|
+
return payload.id || payload.messageID || payload.messageId || payload.info?.id || payload.data?.id || message.id || null;
|
|
10830
|
+
}
|
|
10831
|
+
function openCodeMessageTimestampMs(message = {}, key = "updated") {
|
|
10832
|
+
const payload = openCodeMessagePayload(message);
|
|
10833
|
+
const time = payload.time || payload.info?.time || {};
|
|
10834
|
+
const value = key === "completed" ? payload.time_completed ?? payload.timeCompleted ?? time.completed : key === "created" ? payload.time_created ?? payload.timeCreated ?? time.created : payload.time_updated ?? payload.timeUpdated ?? time.updated ?? payload.time_created ?? payload.timeCreated ?? time.created;
|
|
10835
|
+
const number = Number(value);
|
|
10836
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10837
|
+
}
|
|
10838
|
+
function isOpenCodeAssistantMessageRunning(message = {}) {
|
|
10839
|
+
if (normalizeLooseToken(normalizeOpenCodeMessageRole(message)) !== "assistant") return false;
|
|
10840
|
+
const started = openCodeMessageTimestampMs(message, "created") > 0;
|
|
10841
|
+
if (!started) return false;
|
|
10842
|
+
return openCodeMessageTimestampMs(message, "completed") === 0;
|
|
10843
|
+
}
|
|
10844
|
+
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10 } = {}) {
|
|
10845
|
+
const messages = await readOpenCodeSessionMessages(client, { sessionId, directory, limit });
|
|
10846
|
+
if (!messages) return { ok: false, reason: "message_inspection_unavailable", sessionId, directory };
|
|
10847
|
+
const enriched = messages.map((message, index) => ({
|
|
10848
|
+
message,
|
|
10849
|
+
index,
|
|
10850
|
+
sortTime: openCodeMessageTimestampMs(message, "updated") || index,
|
|
10851
|
+
createdAt: openCodeMessageTimestampMs(message, "created"),
|
|
10852
|
+
completedAt: openCodeMessageTimestampMs(message, "completed")
|
|
10853
|
+
})).sort((a, b) => a.sortTime - b.sortTime || a.index - b.index);
|
|
10854
|
+
const assistantMessages = enriched.filter((entry) => normalizeLooseToken(normalizeOpenCodeMessageRole(entry.message)) === "assistant");
|
|
10855
|
+
const latestAssistant = assistantMessages.at(-1) || null;
|
|
10856
|
+
const latest = enriched.at(-1) || null;
|
|
10857
|
+
const running = latestAssistant ? isOpenCodeAssistantMessageRunning(latestAssistant.message) : false;
|
|
10858
|
+
return {
|
|
10859
|
+
ok: true,
|
|
10860
|
+
sessionId,
|
|
10861
|
+
directory,
|
|
10862
|
+
count: messages.length,
|
|
10863
|
+
running,
|
|
10864
|
+
latestMessageId: latest ? normalizeOpenCodeMessageId(latest.message) : null,
|
|
10865
|
+
latestAssistantMessageId: latestAssistant ? normalizeOpenCodeMessageId(latestAssistant.message) : null,
|
|
10866
|
+
latestAssistantCreatedAt: latestAssistant?.createdAt || 0,
|
|
10867
|
+
latestAssistantCompletedAt: latestAssistant?.completedAt || 0
|
|
10868
|
+
};
|
|
10793
10869
|
}
|
|
10794
10870
|
function summarizeOpenCodeMessages(messages = [], { snippetLength = 160, maxMessages = 50 } = {}) {
|
|
10795
10871
|
const list = Array.isArray(messages) ? messages.slice(0, maxMessages) : [];
|
|
@@ -10929,8 +11005,9 @@ async function probeOpenCodeSessionControl(client, { directory, agent, omitAgent
|
|
|
10929
11005
|
};
|
|
10930
11006
|
}
|
|
10931
11007
|
function openCodeMessageText(message) {
|
|
10932
|
-
const
|
|
10933
|
-
const
|
|
11008
|
+
const payload = openCodeMessagePayload(message);
|
|
11009
|
+
const parts = [payload?.text, payload?.content, payload?.message, payload?.body?.text, payload?.data?.text];
|
|
11010
|
+
const partList = Array.isArray(payload?.parts) ? payload.parts : Array.isArray(payload?.body?.parts) ? payload.body.parts : [];
|
|
10934
11011
|
for (const part of partList) parts.push(part?.text, part?.content);
|
|
10935
11012
|
return parts.filter((value) => typeof value === "string").join("\n");
|
|
10936
11013
|
}
|
|
@@ -11148,6 +11225,7 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
|
|
|
11148
11225
|
entry.cleaning = true;
|
|
11149
11226
|
const cleanup = (async () => {
|
|
11150
11227
|
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
|
|
11228
|
+
if (entry.assignmentWatchdog?.interval) clearInterval(entry.assignmentWatchdog.interval);
|
|
11151
11229
|
const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
|
|
11152
11230
|
if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
|
|
11153
11231
|
const preservedResult = { ok: true, skipped: true, reason: "remote_webhook_preserved" };
|
|
@@ -11161,10 +11239,10 @@ async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WE
|
|
|
11161
11239
|
if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
|
|
11162
11240
|
return result;
|
|
11163
11241
|
}
|
|
11164
|
-
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
11242
|
+
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners, assignmentWatchdog = null } = {}) {
|
|
11165
11243
|
if (!isClickUpWebhookStateActive(state, config)) return null;
|
|
11166
11244
|
const key = managedClickUpWebhookKey({ worktree, state, config });
|
|
11167
|
-
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11245
|
+
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, assignmentWatchdog, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11168
11246
|
activeClickUpWebhookLifecycleRegistry.set(key, entry);
|
|
11169
11247
|
installClickUpWebhookSignalHandlers();
|
|
11170
11248
|
clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
|
|
@@ -11454,19 +11532,54 @@ function scheduleClickUpStartupReconciliation({ config, state = {}, worktree = p
|
|
|
11454
11532
|
timer?.unref?.();
|
|
11455
11533
|
return { scheduled: true, delayMs: normalizedDelayMs, timer };
|
|
11456
11534
|
}
|
|
11457
|
-
|
|
11458
|
-
|
|
11535
|
+
function scheduleClickUpAssignmentWatchdog({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, scheduler = setInterval, intervalMs = config?.opencode?.assignmentWatchdogIntervalMs ?? CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS, saveState = null, reconcile = reconcileClickUpStartup } = {}) {
|
|
11536
|
+
const normalizedIntervalMs = normalizeNonNegativeInteger(intervalMs, CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS);
|
|
11537
|
+
if (!normalizedIntervalMs) {
|
|
11538
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_disabled", webhookId: state?.webhookId || null });
|
|
11539
|
+
return { scheduled: false, intervalMs: 0, reason: "disabled" };
|
|
11540
|
+
}
|
|
11541
|
+
let running = false;
|
|
11542
|
+
const run = () => {
|
|
11543
|
+
if (running) {
|
|
11544
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_tick_skipped", reason: "previous_tick_running" });
|
|
11545
|
+
return;
|
|
11546
|
+
}
|
|
11547
|
+
running = true;
|
|
11548
|
+
const latestState = readClickUpWebhookState(worktree, config);
|
|
11549
|
+
Promise.resolve().then(() => reconcile({
|
|
11550
|
+
config,
|
|
11551
|
+
state: latestState?.webhookId ? latestState : state,
|
|
11552
|
+
worktree,
|
|
11553
|
+
clickupClient,
|
|
11554
|
+
openCodeClient,
|
|
11555
|
+
saveState,
|
|
11556
|
+
assignmentMode: "watchdog"
|
|
11557
|
+
})).catch((error) => {
|
|
11558
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_failed", message: error.message });
|
|
11559
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_failed", message: error.message });
|
|
11560
|
+
}).finally(() => {
|
|
11561
|
+
running = false;
|
|
11562
|
+
});
|
|
11563
|
+
};
|
|
11564
|
+
const interval = scheduler(run, normalizedIntervalMs);
|
|
11565
|
+
interval?.unref?.();
|
|
11566
|
+
clickUpWebhookLifecycleLog(worktree, { type: "assignment_watchdog_scheduled", intervalMs: normalizedIntervalMs, webhookId: state?.webhookId || null });
|
|
11567
|
+
return { scheduled: true, intervalMs: normalizedIntervalMs, interval };
|
|
11568
|
+
}
|
|
11569
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, waitForReadiness = waitForOpenCodeReadiness, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT, assignmentMode = "startup" } = {}) {
|
|
11570
|
+
const watchdogMode = assignmentMode === "watchdog";
|
|
11571
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_started" : "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
11459
11572
|
if (!config || !clickupClient?.listAssignedTasks) {
|
|
11460
11573
|
const skipped = { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
11461
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...skipped });
|
|
11574
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...skipped });
|
|
11462
11575
|
return skipped;
|
|
11463
11576
|
}
|
|
11464
11577
|
const readiness = await waitForReadiness(openCodeClient, { worktree, now, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
11465
11578
|
appendClickUpWebhookLocalLog(worktree, { type: readiness.ok ? "startup_reconciliation_readiness_ready" : "startup_reconciliation_readiness_failed", ...readiness });
|
|
11466
11579
|
if (!readiness.ok) {
|
|
11467
11580
|
const failed = { ok: false, skipped: true, reason: "opencode_not_ready", readiness, assigned: 0, comments: 0, ignored: 0, errors: 1, undelivered: 1, tasks: [], validation: { undelivered: 1, emptyPromptSessions: [] } };
|
|
11468
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
11469
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...failed });
|
|
11581
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_failed" : "startup_reconciliation_failed", reason: failed.reason, readiness });
|
|
11582
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...failed });
|
|
11470
11583
|
return failed;
|
|
11471
11584
|
}
|
|
11472
11585
|
let authorizedUserId = "";
|
|
@@ -11484,6 +11597,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11484
11597
|
if (saveState) saveState(nextState);
|
|
11485
11598
|
};
|
|
11486
11599
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
11600
|
+
const startupCommentStartMs = lastWebhookMs || Math.max(0, now().getTime() - CLICKUP_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS);
|
|
11487
11601
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
11488
11602
|
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
11489
11603
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
@@ -11502,9 +11616,29 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11502
11616
|
routed.ignored += 1;
|
|
11503
11617
|
continue;
|
|
11504
11618
|
}
|
|
11619
|
+
if (watchdogMode) {
|
|
11620
|
+
const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
|
|
11621
|
+
const metadata = normalizeAgentMetadataJson(existingMetadata);
|
|
11622
|
+
const rawSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey);
|
|
11623
|
+
const sessionId = typeof rawSessionId === "string" && rawSessionId.startsWith("ses_") ? rawSessionId : "";
|
|
11624
|
+
const directory = String(getNestedMetadataValue(metadata, "task.worktree") || "").trim();
|
|
11625
|
+
if (sessionId) {
|
|
11626
|
+
try {
|
|
11627
|
+
const activity = await inspectOpenCodeSessionActivity(openCodeClient, { sessionId, directory });
|
|
11628
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity", taskId, sessionId, directory: directory || null, ...activity });
|
|
11629
|
+
if (activity.running) {
|
|
11630
|
+
routed.ignored += 1;
|
|
11631
|
+
routed.tasks.push({ taskId, action: "ignored", ok: true, sessionId, branch: getNestedMetadataValue(metadata, "task.branch") || null, worktree: directory || null, verification: "session_running", delivered: false });
|
|
11632
|
+
continue;
|
|
11633
|
+
}
|
|
11634
|
+
} catch (error) {
|
|
11635
|
+
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity_failed", taskId, sessionId, directory: directory || null, message: error.message });
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11505
11639
|
try {
|
|
11506
11640
|
const result = await routeClickUpWebhookEvent({
|
|
11507
|
-
payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true },
|
|
11641
|
+
payload: { webhook_id: mutableState.webhookId || "startup", event: "taskAssigneeUpdated", task_id: taskId, task, startup_reconciliation: true, assignment_watchdog: watchdogMode },
|
|
11508
11642
|
config,
|
|
11509
11643
|
state: mutableState,
|
|
11510
11644
|
worktree,
|
|
@@ -11554,12 +11688,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11554
11688
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: "error", sessionId: null, reason: "startup_reconciliation_task_failed" });
|
|
11555
11689
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
|
|
11556
11690
|
}
|
|
11557
|
-
if (!
|
|
11691
|
+
if (!startupCommentStartMs || !clickupClient?.getTaskComments) continue;
|
|
11558
11692
|
try {
|
|
11559
|
-
const commentsResponse = await clickupClient.getTaskComments({ taskId, start:
|
|
11693
|
+
const commentsResponse = await clickupClient.getTaskComments({ taskId, start: startupCommentStartMs, limit: CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT });
|
|
11560
11694
|
for (const comment of clickUpCommentListItems(commentsResponse).slice(0, CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT)) {
|
|
11561
11695
|
const commentMs = clickUpCommentUpdatedMs(comment);
|
|
11562
|
-
if (!commentMs || commentMs <=
|
|
11696
|
+
if (!commentMs || commentMs <= startupCommentStartMs) continue;
|
|
11563
11697
|
const authorId = clickUpCommentAuthorId(comment);
|
|
11564
11698
|
if (authorId && (authorId === config.routing.ignoredCommentAuthorId || authorId === authorizedUserId)) continue;
|
|
11565
11699
|
if (!clickUpCommentMentionsProductManager(comment, config.routing)) continue;
|
|
@@ -11586,11 +11720,11 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11586
11720
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
|
|
11587
11721
|
}
|
|
11588
11722
|
}
|
|
11589
|
-
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId);
|
|
11723
|
+
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId && task.verification !== "session_running");
|
|
11590
11724
|
if (emptyPromptSessions.length > 0) {
|
|
11591
11725
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_validation_failed", tasks: emptyPromptSessions });
|
|
11592
11726
|
}
|
|
11593
|
-
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
|
|
11727
|
+
clickUpWebhookLifecycleLog(worktree, { type: watchdogMode ? "assignment_watchdog_finished" : "startup_reconciliation_finished", ...routed });
|
|
11594
11728
|
return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
|
|
11595
11729
|
}
|
|
11596
11730
|
function clickUpWebhookExpectedPath(config) {
|
|
@@ -12904,7 +13038,17 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
12904
13038
|
...listenerState,
|
|
12905
13039
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12906
13040
|
}, clickUpWebhookValidation.config);
|
|
12907
|
-
|
|
13041
|
+
const assignmentWatchdog = scheduleClickUpAssignmentWatchdog({
|
|
13042
|
+
config: clickUpWebhookValidation.config,
|
|
13043
|
+
state: activeState,
|
|
13044
|
+
worktree,
|
|
13045
|
+
clickupClient: lifecycleClickUpClient,
|
|
13046
|
+
openCodeClient: input.client,
|
|
13047
|
+
scheduler: input.assignmentWatchdogScheduler,
|
|
13048
|
+
intervalMs: input.assignmentWatchdogIntervalMs,
|
|
13049
|
+
saveState: (nextState) => writeClickUpWebhookState(worktree, nextState, clickUpWebhookValidation.config)
|
|
13050
|
+
});
|
|
13051
|
+
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry, assignmentWatchdog });
|
|
12908
13052
|
scheduleClickUpStartupReconciliation({
|
|
12909
13053
|
config: clickUpWebhookValidation.config,
|
|
12910
13054
|
state: activeState,
|
|
@@ -13560,7 +13704,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
13560
13704
|
}
|
|
13561
13705
|
};
|
|
13562
13706
|
}
|
|
13563
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13707
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, compactPromptPath, createClickUpApiClient, createTestClickUpApiClient, createOpenCodeSession, createOpenCodeSessionControl, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpTaskWorktreeForWebhook, ensureClickUpTaskWorktreeOpenChamber, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, inspectOpenCodeSessionActivity, isClickUpDerivedWorktreeSibling, isClickUpSubtaskRoute, isClickUpWebhookStateActive, isSameOrNestedPath, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, openCodeSessionExists, promptOpenCodeSessionControl, probeOpenCodeSessionControl, readClickUpCommentLedger, readClickUpWebhookState, readOpenCodeSessionControl, readOpenCodeSessionMessages, reconcileClickUpStartup, registerOpenChamberClickUpWorktree, scheduleClickUpAssignmentWatchdog, scheduleClickUpStartupReconciliation, syncOpenChamberWorktreeVisibility, waitForOpenCodeReadiness, recordClickUpCommentVersionProcessed, resyncClickUpWebhookForSignatureDrift, resolveIncludeFile, resolveIncludes, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, summarizeOpenCodeMessages, verifyOpenCodeSessionEventDelivery, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatRepairResult, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, optimaRepairDependencies, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, planOptimaRepair, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
13564
13708
|
|
|
13565
13709
|
// src/sanitize_cli.js
|
|
13566
13710
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|