@defend-tech/opencode-optima 0.1.69 → 0.1.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +93 -17
- package/dist/sanitize_cli.js +93 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8530,6 +8530,7 @@ var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
|
8530
8530
|
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
8531
8531
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS = 6 * 60 * 60 * 1e3;
|
|
8532
8532
|
var CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8533
|
+
var CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS = 15 * 60 * 1e3;
|
|
8533
8534
|
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8534
8535
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8535
8536
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
@@ -9816,6 +9817,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9816
9817
|
assignmentWatchdogIntervalMs: normalizeNonNegativeInteger(
|
|
9817
9818
|
opencode.assignment_watchdog_interval_ms ?? opencode.assignmentWatchdogIntervalMs ?? raw.assignment_watchdog_interval_ms ?? raw.assignmentWatchdogIntervalMs,
|
|
9818
9819
|
CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS
|
|
9820
|
+
),
|
|
9821
|
+
assignmentWatchdogRunningGraceMs: normalizeNonNegativeInteger(
|
|
9822
|
+
opencode.assignment_watchdog_running_grace_ms ?? opencode.assignmentWatchdogRunningGraceMs ?? raw.assignment_watchdog_running_grace_ms ?? raw.assignmentWatchdogRunningGraceMs,
|
|
9823
|
+
CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS
|
|
9819
9824
|
)
|
|
9820
9825
|
},
|
|
9821
9826
|
openchamber: {
|
|
@@ -10649,20 +10654,25 @@ function appendDirectoryQuery(url, directory) {
|
|
|
10649
10654
|
const separator = url.includes("?") ? "&" : "?";
|
|
10650
10655
|
return `${url}${separator}directory=${encodeURIComponent(value)}`;
|
|
10651
10656
|
}
|
|
10652
|
-
|
|
10657
|
+
function normalizeOpenCodePromptDelivery(value, fallback = "queue") {
|
|
10658
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
10659
|
+
return normalized === "steer" || normalized === "queue" ? normalized : fallback;
|
|
10660
|
+
}
|
|
10661
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, directory, fetchImpl = globalThis.fetch, legacyOnly = false, delivery = "queue" } = {}) {
|
|
10653
10662
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
10654
10663
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
10655
10664
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
10656
10665
|
const encodedSession = encodeURIComponent(sessionId);
|
|
10666
|
+
const promptDelivery = normalizeOpenCodePromptDelivery(delivery, "queue");
|
|
10657
10667
|
const attempts = [
|
|
10658
10668
|
legacyOnly ? null : {
|
|
10659
10669
|
name: "v2 prompt",
|
|
10660
10670
|
url: appendDirectoryQuery(`${root}/api/session/${encodedSession}/prompt`, directory),
|
|
10661
|
-
body: { prompt: { text }, delivery:
|
|
10671
|
+
body: { prompt: { text }, delivery: promptDelivery, resume: true },
|
|
10662
10672
|
accept: (response, data) => {
|
|
10663
10673
|
if (!response.ok) return null;
|
|
10664
10674
|
if (!openCodePromptAdmissionVerification(data, sessionId)) throw new Error("OpenCode v2 prompt response did not include a valid prompt admission.");
|
|
10665
|
-
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, data: data.data, response: data };
|
|
10675
|
+
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, delivery: promptDelivery, data: data.data, response: data };
|
|
10666
10676
|
}
|
|
10667
10677
|
},
|
|
10668
10678
|
{
|
|
@@ -10718,7 +10728,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
|
|
|
10718
10728
|
}
|
|
10719
10729
|
throw firstError;
|
|
10720
10730
|
}
|
|
10721
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true } = {}) {
|
|
10731
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true, directDelivery = "queue" } = {}) {
|
|
10722
10732
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
10723
10733
|
const parts = [{ type: "text", text }];
|
|
10724
10734
|
const flatPayload = { directory, agent, parts };
|
|
@@ -10742,7 +10752,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
10742
10752
|
firstError ??= error;
|
|
10743
10753
|
}
|
|
10744
10754
|
}
|
|
10745
|
-
if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly });
|
|
10755
|
+
if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly, delivery: directDelivery });
|
|
10746
10756
|
if (firstError) throw firstError;
|
|
10747
10757
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
10748
10758
|
}
|
|
@@ -10771,6 +10781,22 @@ async function readOpenCodeSessionMessages(client, { sessionId, directory, limit
|
|
|
10771
10781
|
if (directory) query.directory = directory;
|
|
10772
10782
|
return normalizeOpenCodeSessionMessages(await client.session.messages({ path: { id: sessionId }, query }));
|
|
10773
10783
|
}
|
|
10784
|
+
async function readOpenCodeChildSessions(client, { sessionId, directory } = {}) {
|
|
10785
|
+
if (typeof client?.session?.children !== "function") return [];
|
|
10786
|
+
const attempts = [
|
|
10787
|
+
{ path: { id: sessionId }, query: directory ? { directory } : {} },
|
|
10788
|
+
{ path: { sessionID: sessionId }, query: directory ? { directory } : {} },
|
|
10789
|
+
{ sessionID: sessionId, ...directory ? { directory } : {} }
|
|
10790
|
+
];
|
|
10791
|
+
for (const attempt of attempts) {
|
|
10792
|
+
try {
|
|
10793
|
+
const result = await client.session.children(attempt);
|
|
10794
|
+
return normalizeOpenCodeSessionCollection(result?.data ?? result);
|
|
10795
|
+
} catch {
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
10798
|
+
return [];
|
|
10799
|
+
}
|
|
10774
10800
|
function openCodeResultSummary(result) {
|
|
10775
10801
|
const data = result?.data ?? result;
|
|
10776
10802
|
return {
|
|
@@ -10831,13 +10857,28 @@ function openCodeMessageTimestampMs(message = {}, key = "updated") {
|
|
|
10831
10857
|
const number = Number(value);
|
|
10832
10858
|
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10833
10859
|
}
|
|
10834
|
-
function
|
|
10860
|
+
function openCodeSessionTimestampMs(session = {}, key = "updated") {
|
|
10861
|
+
const time = session?.time || session?.info?.time || {};
|
|
10862
|
+
const value = key === "created" ? session.time_created ?? session.timeCreated ?? time.created : session.time_updated ?? session.timeUpdated ?? time.updated ?? session.time_created ?? session.timeCreated ?? time.created;
|
|
10863
|
+
const number = Number(value);
|
|
10864
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10865
|
+
}
|
|
10866
|
+
function openCodeNowMs(now = /* @__PURE__ */ new Date()) {
|
|
10867
|
+
const value = typeof now === "function" ? now() : now;
|
|
10868
|
+
const number = Number(value instanceof Date ? value.getTime() : new Date(value).getTime());
|
|
10869
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10870
|
+
}
|
|
10871
|
+
function isOpenCodeAssistantMessageRunning(message = {}, { latestActivityAt = 0, now = /* @__PURE__ */ new Date(), runningGraceMs = CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS } = {}) {
|
|
10835
10872
|
if (normalizeLooseToken(normalizeOpenCodeMessageRole(message)) !== "assistant") return false;
|
|
10836
10873
|
const started = openCodeMessageTimestampMs(message, "created") > 0;
|
|
10837
10874
|
if (!started) return false;
|
|
10838
|
-
|
|
10875
|
+
if (openCodeMessageTimestampMs(message, "completed") !== 0) return false;
|
|
10876
|
+
const nowMs = openCodeNowMs(now);
|
|
10877
|
+
const activityAt = Number(latestActivityAt) || openCodeMessageTimestampMs(message, "updated") || openCodeMessageTimestampMs(message, "created");
|
|
10878
|
+
if (!Number.isFinite(nowMs) || nowMs <= 0 || !Number.isFinite(activityAt) || activityAt <= 0) return false;
|
|
10879
|
+
return nowMs - activityAt <= Math.max(0, Number(runningGraceMs) || 0);
|
|
10839
10880
|
}
|
|
10840
|
-
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10 } = {}) {
|
|
10881
|
+
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10, now = /* @__PURE__ */ new Date(), runningGraceMs = CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS } = {}) {
|
|
10841
10882
|
const messages = await readOpenCodeSessionMessages(client, { sessionId, directory, limit });
|
|
10842
10883
|
if (!messages) return { ok: false, reason: "message_inspection_unavailable", sessionId, directory };
|
|
10843
10884
|
const enriched = messages.map((message, index) => ({
|
|
@@ -10847,20 +10888,45 @@ async function inspectOpenCodeSessionActivity(client, { sessionId, directory, li
|
|
|
10847
10888
|
createdAt: openCodeMessageTimestampMs(message, "created"),
|
|
10848
10889
|
completedAt: openCodeMessageTimestampMs(message, "completed")
|
|
10849
10890
|
})).sort((a, b) => a.sortTime - b.sortTime || a.index - b.index);
|
|
10891
|
+
const childSessions = await readOpenCodeChildSessions(client, { sessionId, directory });
|
|
10892
|
+
const childActivities = childSessions.map((session, index) => ({
|
|
10893
|
+
session,
|
|
10894
|
+
index,
|
|
10895
|
+
id: session.id || session.sessionID || session.sessionId || null,
|
|
10896
|
+
agent: session.agent || session.mode?.agent || session.metadata?.agent || "",
|
|
10897
|
+
updatedAt: openCodeSessionTimestampMs(session, "updated")
|
|
10898
|
+
})).filter((entry) => entry.updatedAt > 0).sort((a, b) => a.updatedAt - b.updatedAt || a.index - b.index);
|
|
10850
10899
|
const assistantMessages = enriched.filter((entry) => normalizeLooseToken(normalizeOpenCodeMessageRole(entry.message)) === "assistant");
|
|
10851
10900
|
const latestAssistant = assistantMessages.at(-1) || null;
|
|
10852
10901
|
const latest = enriched.at(-1) || null;
|
|
10853
|
-
const
|
|
10902
|
+
const latestChild = childActivities.at(-1) || null;
|
|
10903
|
+
const latestMessageActivityAt = latest?.sortTime || 0;
|
|
10904
|
+
const latestChildActivityAt = latestChild?.updatedAt || 0;
|
|
10905
|
+
const latestActivityAt = Math.max(latestMessageActivityAt, latestChildActivityAt);
|
|
10906
|
+
const nowMs = openCodeNowMs(now);
|
|
10907
|
+
const latestActivityAgeMs = Number.isFinite(nowMs) && latestActivityAt > 0 ? Math.max(0, nowMs - latestActivityAt) : null;
|
|
10908
|
+
const assistantRunning = latestAssistant ? isOpenCodeAssistantMessageRunning(latestAssistant.message, { latestActivityAt, now, runningGraceMs }) : false;
|
|
10909
|
+
const childRunning = latestChildActivityAt > 0 && latestActivityAgeMs !== null && latestActivityAgeMs <= Math.max(0, Number(runningGraceMs) || 0);
|
|
10910
|
+
const running = assistantRunning || childRunning;
|
|
10911
|
+
const runningReason = running ? childRunning && latestChildActivityAt >= latestMessageActivityAt ? "recent_child_session_activity" : "recent_incomplete_assistant" : latestAssistant && latestAssistant.completedAt === 0 ? "stale_incomplete_assistant" : "not_running";
|
|
10854
10912
|
return {
|
|
10855
10913
|
ok: true,
|
|
10856
10914
|
sessionId,
|
|
10857
10915
|
directory,
|
|
10858
10916
|
count: messages.length,
|
|
10859
10917
|
running,
|
|
10918
|
+
runningReason,
|
|
10919
|
+
runningGraceMs,
|
|
10920
|
+
latestActivityAt,
|
|
10921
|
+
latestActivityAgeMs,
|
|
10860
10922
|
latestMessageId: latest ? normalizeOpenCodeMessageId(latest.message) : null,
|
|
10861
10923
|
latestAssistantMessageId: latestAssistant ? normalizeOpenCodeMessageId(latestAssistant.message) : null,
|
|
10862
10924
|
latestAssistantCreatedAt: latestAssistant?.createdAt || 0,
|
|
10863
|
-
latestAssistantCompletedAt: latestAssistant?.completedAt || 0
|
|
10925
|
+
latestAssistantCompletedAt: latestAssistant?.completedAt || 0,
|
|
10926
|
+
latestChildSessionId: latestChild?.id || null,
|
|
10927
|
+
latestChildAgent: latestChild?.agent || null,
|
|
10928
|
+
latestChildUpdatedAt: latestChild?.updatedAt || 0,
|
|
10929
|
+
childSessionCount: childSessions.length
|
|
10864
10930
|
};
|
|
10865
10931
|
}
|
|
10866
10932
|
function summarizeOpenCodeMessages(messages = [], { snippetLength = 160, maxMessages = 50 } = {}) {
|
|
@@ -11059,11 +11125,11 @@ function openCodeBlockingPromptVerification(result, sessionId) {
|
|
|
11059
11125
|
if (parts.length > 0 || messageId) return { ok: true, method: parts.length > 0 ? "blocking_prompt_parts" : "blocking_prompt_message", messageId: messageId ? String(messageId) : null, sessionId: deliveredSessionId ? String(deliveredSessionId) : String(sessionId), parts: parts.length };
|
|
11060
11126
|
return null;
|
|
11061
11127
|
}
|
|
11062
|
-
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
11128
|
+
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, directDelivery = "queue", acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
11063
11129
|
const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, directory, limit: 50 }).catch(() => null);
|
|
11064
11130
|
let sendResult;
|
|
11065
11131
|
try {
|
|
11066
|
-
sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
|
|
11132
|
+
sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, directDelivery, allowDirectFallback: directPrompt });
|
|
11067
11133
|
} catch (error) {
|
|
11068
11134
|
const reason2 = error.message || "message_delivery_failed";
|
|
11069
11135
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: reason2, fallbackAttempted: false });
|
|
@@ -11086,7 +11152,13 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
11086
11152
|
}
|
|
11087
11153
|
if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
|
|
11088
11154
|
if (admissionVerification && acceptPromptAdmission) {
|
|
11089
|
-
|
|
11155
|
+
appendClickUpWebhookLocalLog(worktree, {
|
|
11156
|
+
type: "message_delivery_admission_not_sufficient",
|
|
11157
|
+
taskId,
|
|
11158
|
+
sessionId,
|
|
11159
|
+
admission: admissionVerification,
|
|
11160
|
+
policy: "clickup_routing_requires_visible_delivery"
|
|
11161
|
+
});
|
|
11090
11162
|
}
|
|
11091
11163
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, directory, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
11092
11164
|
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
|
|
@@ -11489,10 +11561,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
11489
11561
|
const sessionId = String(existingSessionId);
|
|
11490
11562
|
if (await sessionExists(openCodeClient, sessionId, { directory: taskRoute.worktree })) {
|
|
11491
11563
|
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
11492
|
-
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
11564
|
+
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", directDelivery: "steer", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
11493
11565
|
if (!delivery.ok) {
|
|
11494
|
-
|
|
11495
|
-
return finish(recovery2);
|
|
11566
|
+
return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, replacementAttempted: false });
|
|
11496
11567
|
}
|
|
11497
11568
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
11498
11569
|
}
|
|
@@ -11620,7 +11691,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11620
11691
|
const directory = String(getNestedMetadataValue(metadata, "task.worktree") || "").trim();
|
|
11621
11692
|
if (sessionId) {
|
|
11622
11693
|
try {
|
|
11623
|
-
const activity = await inspectOpenCodeSessionActivity(openCodeClient, {
|
|
11694
|
+
const activity = await inspectOpenCodeSessionActivity(openCodeClient, {
|
|
11695
|
+
sessionId,
|
|
11696
|
+
directory,
|
|
11697
|
+
now,
|
|
11698
|
+
runningGraceMs: config.opencode.assignmentWatchdogRunningGraceMs
|
|
11699
|
+
});
|
|
11624
11700
|
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity", taskId, sessionId, directory: directory || null, ...activity });
|
|
11625
11701
|
if (activity.running) {
|
|
11626
11702
|
routed.ignored += 1;
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8537,6 +8537,7 @@ var CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT = 100;
|
|
|
8537
8537
|
var CLICKUP_WEBHOOK_STARTUP_RECONCILIATION_DELAY_MS = 3e4;
|
|
8538
8538
|
var CLICKUP_WEBHOOK_STARTUP_COMMENT_LOOKBACK_MS = 6 * 60 * 60 * 1e3;
|
|
8539
8539
|
var CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS = 5 * 60 * 1e3;
|
|
8540
|
+
var CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS = 15 * 60 * 1e3;
|
|
8540
8541
|
var CLICKUP_WORKTREE_FAILURE_COMMENT_DEDUPE_MS = 10 * 60 * 1e3;
|
|
8541
8542
|
var CLICKUP_WEBHOOK_LOG_LEVELS = /* @__PURE__ */ new Set(["error", "info", "verbose"]);
|
|
8542
8543
|
var CLICKUP_WEBHOOK_REDACTED = "[REDACTED]";
|
|
@@ -9823,6 +9824,10 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9823
9824
|
assignmentWatchdogIntervalMs: normalizeNonNegativeInteger(
|
|
9824
9825
|
opencode.assignment_watchdog_interval_ms ?? opencode.assignmentWatchdogIntervalMs ?? raw.assignment_watchdog_interval_ms ?? raw.assignmentWatchdogIntervalMs,
|
|
9825
9826
|
CLICKUP_ASSIGNMENT_WATCHDOG_INTERVAL_MS
|
|
9827
|
+
),
|
|
9828
|
+
assignmentWatchdogRunningGraceMs: normalizeNonNegativeInteger(
|
|
9829
|
+
opencode.assignment_watchdog_running_grace_ms ?? opencode.assignmentWatchdogRunningGraceMs ?? raw.assignment_watchdog_running_grace_ms ?? raw.assignmentWatchdogRunningGraceMs,
|
|
9830
|
+
CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS
|
|
9826
9831
|
)
|
|
9827
9832
|
},
|
|
9828
9833
|
openchamber: {
|
|
@@ -10656,20 +10661,25 @@ function appendDirectoryQuery(url, directory) {
|
|
|
10656
10661
|
const separator = url.includes("?") ? "&" : "?";
|
|
10657
10662
|
return `${url}${separator}directory=${encodeURIComponent(value)}`;
|
|
10658
10663
|
}
|
|
10659
|
-
|
|
10664
|
+
function normalizeOpenCodePromptDelivery(value, fallback = "queue") {
|
|
10665
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
10666
|
+
return normalized === "steer" || normalized === "queue" ? normalized : fallback;
|
|
10667
|
+
}
|
|
10668
|
+
async function sendOpenCodeSessionEventDirect({ baseUrl, sessionId, text, agent, directory, fetchImpl = globalThis.fetch, legacyOnly = false, delivery = "queue" } = {}) {
|
|
10660
10669
|
if (typeof fetchImpl !== "function") throw new Error("OpenCode direct prompt delivery requires fetch.");
|
|
10661
10670
|
const root = normalizeOpenCodeBaseUrl(baseUrl, "");
|
|
10662
10671
|
if (!root) throw new Error("OpenCode direct prompt delivery requires a base URL.");
|
|
10663
10672
|
const encodedSession = encodeURIComponent(sessionId);
|
|
10673
|
+
const promptDelivery = normalizeOpenCodePromptDelivery(delivery, "queue");
|
|
10664
10674
|
const attempts = [
|
|
10665
10675
|
legacyOnly ? null : {
|
|
10666
10676
|
name: "v2 prompt",
|
|
10667
10677
|
url: appendDirectoryQuery(`${root}/api/session/${encodedSession}/prompt`, directory),
|
|
10668
|
-
body: { prompt: { text }, delivery:
|
|
10678
|
+
body: { prompt: { text }, delivery: promptDelivery, resume: true },
|
|
10669
10679
|
accept: (response, data) => {
|
|
10670
10680
|
if (!response.ok) return null;
|
|
10671
10681
|
if (!openCodePromptAdmissionVerification(data, sessionId)) throw new Error("OpenCode v2 prompt response did not include a valid prompt admission.");
|
|
10672
|
-
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, data: data.data, response: data };
|
|
10682
|
+
return { ok: true, method: "http", endpoint: "/api/session/{sessionID}/prompt", status: response.status, delivery: promptDelivery, data: data.data, response: data };
|
|
10673
10683
|
}
|
|
10674
10684
|
},
|
|
10675
10685
|
{
|
|
@@ -10725,7 +10735,7 @@ async function callOpenCodePromptWithFallbacks(method, sessionId, flatPayload, s
|
|
|
10725
10735
|
}
|
|
10726
10736
|
throw firstError;
|
|
10727
10737
|
}
|
|
10728
|
-
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true } = {}) {
|
|
10738
|
+
async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, directory, opencodeBaseUrl, baseUrl, fetchImpl, direct = false, legacyOnly = false, allowDirectFallback = true, directDelivery = "queue" } = {}) {
|
|
10729
10739
|
const directBaseUrl = opencodeBaseUrl || baseUrl;
|
|
10730
10740
|
const parts = [{ type: "text", text }];
|
|
10731
10741
|
const flatPayload = { directory, agent, parts };
|
|
@@ -10749,7 +10759,7 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
10749
10759
|
firstError ??= error;
|
|
10750
10760
|
}
|
|
10751
10761
|
}
|
|
10752
|
-
if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly });
|
|
10762
|
+
if (allowDirectFallback && directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, agent, directory, fetchImpl, legacyOnly, delivery: directDelivery });
|
|
10753
10763
|
if (firstError) throw firstError;
|
|
10754
10764
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
10755
10765
|
}
|
|
@@ -10778,6 +10788,22 @@ async function readOpenCodeSessionMessages(client, { sessionId, directory, limit
|
|
|
10778
10788
|
if (directory) query.directory = directory;
|
|
10779
10789
|
return normalizeOpenCodeSessionMessages(await client.session.messages({ path: { id: sessionId }, query }));
|
|
10780
10790
|
}
|
|
10791
|
+
async function readOpenCodeChildSessions(client, { sessionId, directory } = {}) {
|
|
10792
|
+
if (typeof client?.session?.children !== "function") return [];
|
|
10793
|
+
const attempts = [
|
|
10794
|
+
{ path: { id: sessionId }, query: directory ? { directory } : {} },
|
|
10795
|
+
{ path: { sessionID: sessionId }, query: directory ? { directory } : {} },
|
|
10796
|
+
{ sessionID: sessionId, ...directory ? { directory } : {} }
|
|
10797
|
+
];
|
|
10798
|
+
for (const attempt of attempts) {
|
|
10799
|
+
try {
|
|
10800
|
+
const result = await client.session.children(attempt);
|
|
10801
|
+
return normalizeOpenCodeSessionCollection(result?.data ?? result);
|
|
10802
|
+
} catch {
|
|
10803
|
+
}
|
|
10804
|
+
}
|
|
10805
|
+
return [];
|
|
10806
|
+
}
|
|
10781
10807
|
function openCodeResultSummary(result) {
|
|
10782
10808
|
const data = result?.data ?? result;
|
|
10783
10809
|
return {
|
|
@@ -10838,13 +10864,28 @@ function openCodeMessageTimestampMs(message = {}, key = "updated") {
|
|
|
10838
10864
|
const number = Number(value);
|
|
10839
10865
|
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10840
10866
|
}
|
|
10841
|
-
function
|
|
10867
|
+
function openCodeSessionTimestampMs(session = {}, key = "updated") {
|
|
10868
|
+
const time = session?.time || session?.info?.time || {};
|
|
10869
|
+
const value = key === "created" ? session.time_created ?? session.timeCreated ?? time.created : session.time_updated ?? session.timeUpdated ?? time.updated ?? session.time_created ?? session.timeCreated ?? time.created;
|
|
10870
|
+
const number = Number(value);
|
|
10871
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10872
|
+
}
|
|
10873
|
+
function openCodeNowMs(now = /* @__PURE__ */ new Date()) {
|
|
10874
|
+
const value = typeof now === "function" ? now() : now;
|
|
10875
|
+
const number = Number(value instanceof Date ? value.getTime() : new Date(value).getTime());
|
|
10876
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
10877
|
+
}
|
|
10878
|
+
function isOpenCodeAssistantMessageRunning(message = {}, { latestActivityAt = 0, now = /* @__PURE__ */ new Date(), runningGraceMs = CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS } = {}) {
|
|
10842
10879
|
if (normalizeLooseToken(normalizeOpenCodeMessageRole(message)) !== "assistant") return false;
|
|
10843
10880
|
const started = openCodeMessageTimestampMs(message, "created") > 0;
|
|
10844
10881
|
if (!started) return false;
|
|
10845
|
-
|
|
10882
|
+
if (openCodeMessageTimestampMs(message, "completed") !== 0) return false;
|
|
10883
|
+
const nowMs = openCodeNowMs(now);
|
|
10884
|
+
const activityAt = Number(latestActivityAt) || openCodeMessageTimestampMs(message, "updated") || openCodeMessageTimestampMs(message, "created");
|
|
10885
|
+
if (!Number.isFinite(nowMs) || nowMs <= 0 || !Number.isFinite(activityAt) || activityAt <= 0) return false;
|
|
10886
|
+
return nowMs - activityAt <= Math.max(0, Number(runningGraceMs) || 0);
|
|
10846
10887
|
}
|
|
10847
|
-
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10 } = {}) {
|
|
10888
|
+
async function inspectOpenCodeSessionActivity(client, { sessionId, directory, limit = 10, now = /* @__PURE__ */ new Date(), runningGraceMs = CLICKUP_ASSIGNMENT_WATCHDOG_RUNNING_GRACE_MS } = {}) {
|
|
10848
10889
|
const messages = await readOpenCodeSessionMessages(client, { sessionId, directory, limit });
|
|
10849
10890
|
if (!messages) return { ok: false, reason: "message_inspection_unavailable", sessionId, directory };
|
|
10850
10891
|
const enriched = messages.map((message, index) => ({
|
|
@@ -10854,20 +10895,45 @@ async function inspectOpenCodeSessionActivity(client, { sessionId, directory, li
|
|
|
10854
10895
|
createdAt: openCodeMessageTimestampMs(message, "created"),
|
|
10855
10896
|
completedAt: openCodeMessageTimestampMs(message, "completed")
|
|
10856
10897
|
})).sort((a, b) => a.sortTime - b.sortTime || a.index - b.index);
|
|
10898
|
+
const childSessions = await readOpenCodeChildSessions(client, { sessionId, directory });
|
|
10899
|
+
const childActivities = childSessions.map((session, index) => ({
|
|
10900
|
+
session,
|
|
10901
|
+
index,
|
|
10902
|
+
id: session.id || session.sessionID || session.sessionId || null,
|
|
10903
|
+
agent: session.agent || session.mode?.agent || session.metadata?.agent || "",
|
|
10904
|
+
updatedAt: openCodeSessionTimestampMs(session, "updated")
|
|
10905
|
+
})).filter((entry) => entry.updatedAt > 0).sort((a, b) => a.updatedAt - b.updatedAt || a.index - b.index);
|
|
10857
10906
|
const assistantMessages = enriched.filter((entry) => normalizeLooseToken(normalizeOpenCodeMessageRole(entry.message)) === "assistant");
|
|
10858
10907
|
const latestAssistant = assistantMessages.at(-1) || null;
|
|
10859
10908
|
const latest = enriched.at(-1) || null;
|
|
10860
|
-
const
|
|
10909
|
+
const latestChild = childActivities.at(-1) || null;
|
|
10910
|
+
const latestMessageActivityAt = latest?.sortTime || 0;
|
|
10911
|
+
const latestChildActivityAt = latestChild?.updatedAt || 0;
|
|
10912
|
+
const latestActivityAt = Math.max(latestMessageActivityAt, latestChildActivityAt);
|
|
10913
|
+
const nowMs = openCodeNowMs(now);
|
|
10914
|
+
const latestActivityAgeMs = Number.isFinite(nowMs) && latestActivityAt > 0 ? Math.max(0, nowMs - latestActivityAt) : null;
|
|
10915
|
+
const assistantRunning = latestAssistant ? isOpenCodeAssistantMessageRunning(latestAssistant.message, { latestActivityAt, now, runningGraceMs }) : false;
|
|
10916
|
+
const childRunning = latestChildActivityAt > 0 && latestActivityAgeMs !== null && latestActivityAgeMs <= Math.max(0, Number(runningGraceMs) || 0);
|
|
10917
|
+
const running = assistantRunning || childRunning;
|
|
10918
|
+
const runningReason = running ? childRunning && latestChildActivityAt >= latestMessageActivityAt ? "recent_child_session_activity" : "recent_incomplete_assistant" : latestAssistant && latestAssistant.completedAt === 0 ? "stale_incomplete_assistant" : "not_running";
|
|
10861
10919
|
return {
|
|
10862
10920
|
ok: true,
|
|
10863
10921
|
sessionId,
|
|
10864
10922
|
directory,
|
|
10865
10923
|
count: messages.length,
|
|
10866
10924
|
running,
|
|
10925
|
+
runningReason,
|
|
10926
|
+
runningGraceMs,
|
|
10927
|
+
latestActivityAt,
|
|
10928
|
+
latestActivityAgeMs,
|
|
10867
10929
|
latestMessageId: latest ? normalizeOpenCodeMessageId(latest.message) : null,
|
|
10868
10930
|
latestAssistantMessageId: latestAssistant ? normalizeOpenCodeMessageId(latestAssistant.message) : null,
|
|
10869
10931
|
latestAssistantCreatedAt: latestAssistant?.createdAt || 0,
|
|
10870
|
-
latestAssistantCompletedAt: latestAssistant?.completedAt || 0
|
|
10932
|
+
latestAssistantCompletedAt: latestAssistant?.completedAt || 0,
|
|
10933
|
+
latestChildSessionId: latestChild?.id || null,
|
|
10934
|
+
latestChildAgent: latestChild?.agent || null,
|
|
10935
|
+
latestChildUpdatedAt: latestChild?.updatedAt || 0,
|
|
10936
|
+
childSessionCount: childSessions.length
|
|
10871
10937
|
};
|
|
10872
10938
|
}
|
|
10873
10939
|
function summarizeOpenCodeMessages(messages = [], { snippetLength = 160, maxMessages = 50 } = {}) {
|
|
@@ -11066,11 +11132,11 @@ function openCodeBlockingPromptVerification(result, sessionId) {
|
|
|
11066
11132
|
if (parts.length > 0 || messageId) return { ok: true, method: parts.length > 0 ? "blocking_prompt_parts" : "blocking_prompt_message", messageId: messageId ? String(messageId) : null, sessionId: deliveredSessionId ? String(deliveredSessionId) : String(sessionId), parts: parts.length };
|
|
11067
11133
|
return null;
|
|
11068
11134
|
}
|
|
11069
|
-
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
11135
|
+
async function deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent, text, directory, opencodeBaseUrl, directPrompt = false, directDelivery = "queue", acceptPromptAdmission = false, eventMarkers = [], verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, applyBlockerOnFailure = true } = {}) {
|
|
11070
11136
|
const beforeMessages = await readOpenCodeSessionMessages(openCodeClient, { sessionId, directory, limit: 50 }).catch(() => null);
|
|
11071
11137
|
let sendResult;
|
|
11072
11138
|
try {
|
|
11073
|
-
sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, allowDirectFallback: directPrompt });
|
|
11139
|
+
sendResult = await sendSessionEvent(openCodeClient, { sessionId, agent, text, directory, opencodeBaseUrl, direct: directPrompt, directDelivery, allowDirectFallback: directPrompt });
|
|
11074
11140
|
} catch (error) {
|
|
11075
11141
|
const reason2 = error.message || "message_delivery_failed";
|
|
11076
11142
|
appendClickUpWebhookLocalLog(worktree, { type: "message_delivery_failed", taskId, sessionId, reason: reason2, fallbackAttempted: false });
|
|
@@ -11093,7 +11159,13 @@ async function deliverClickUpSessionEventWithVerification({ openCodeClient, send
|
|
|
11093
11159
|
}
|
|
11094
11160
|
if (blockingPromptVerification) return { ok: true, verification: blockingPromptVerification, admissionVerification: null, fallback: false };
|
|
11095
11161
|
if (admissionVerification && acceptPromptAdmission) {
|
|
11096
|
-
|
|
11162
|
+
appendClickUpWebhookLocalLog(worktree, {
|
|
11163
|
+
type: "message_delivery_admission_not_sufficient",
|
|
11164
|
+
taskId,
|
|
11165
|
+
sessionId,
|
|
11166
|
+
admission: admissionVerification,
|
|
11167
|
+
policy: "clickup_routing_requires_visible_delivery"
|
|
11168
|
+
});
|
|
11097
11169
|
}
|
|
11098
11170
|
let verification = await verifySessionEventDelivery(openCodeClient, { sessionId, directory, beforeMessages, expectedText: text, markers: eventMarkers });
|
|
11099
11171
|
if (verification?.ok) return { ok: true, verification, admissionVerification, fallback: false };
|
|
@@ -11496,10 +11568,9 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
11496
11568
|
const sessionId = String(existingSessionId);
|
|
11497
11569
|
if (await sessionExists(openCodeClient, sessionId, { directory: taskRoute.worktree })) {
|
|
11498
11570
|
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
11499
|
-
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
11571
|
+
const delivery = await deliverClickUpSessionEventWithVerification({ openCodeClient, sendSessionEvent, clickupClient, worktree, taskId, sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl, directPrompt: config.opencode?.promptDelivery === "http", directDelivery: "steer", acceptPromptAdmission: config.opencode?.acceptPromptAdmission === true, eventMarkers: [taskId, eventType], verifySessionEventDelivery, applyBlockerOnFailure: false });
|
|
11500
11572
|
if (!delivery.ok) {
|
|
11501
|
-
|
|
11502
|
-
return finish(recovery2);
|
|
11573
|
+
return finish({ ...delivery, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, replacementAttempted: false });
|
|
11503
11574
|
}
|
|
11504
11575
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath: routingMetadata.evidence_path, deliveryVerification: delivery.verification, deliveryAdmission: delivery.admissionVerification, deliveryFallback: delivery.fallback, deliveryAttempts: delivery.fallback ? 2 : 1 });
|
|
11505
11576
|
}
|
|
@@ -11627,7 +11698,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
11627
11698
|
const directory = String(getNestedMetadataValue(metadata, "task.worktree") || "").trim();
|
|
11628
11699
|
if (sessionId) {
|
|
11629
11700
|
try {
|
|
11630
|
-
const activity = await inspectOpenCodeSessionActivity(openCodeClient, {
|
|
11701
|
+
const activity = await inspectOpenCodeSessionActivity(openCodeClient, {
|
|
11702
|
+
sessionId,
|
|
11703
|
+
directory,
|
|
11704
|
+
now,
|
|
11705
|
+
runningGraceMs: config.opencode.assignmentWatchdogRunningGraceMs
|
|
11706
|
+
});
|
|
11631
11707
|
appendClickUpWebhookLocalLog(worktree, { type: "assignment_watchdog_session_activity", taskId, sessionId, directory: directory || null, ...activity });
|
|
11632
11708
|
if (activity.running) {
|
|
11633
11709
|
routed.ignored += 1;
|