@useorgx/openclaw-plugin 0.3.1 → 0.3.2
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/dashboard/dist/assets/MissionControlView-CthHdl6R.js +1 -0
- package/dashboard/dist/assets/SessionInspector-Dq0Z5WMo.js +1 -0
- package/dashboard/dist/assets/index-CoLgC4zE.js +11 -0
- package/dashboard/dist/assets/index-jfEYE0kO.css +1 -0
- package/dashboard/dist/assets/motion-CVDprFZg.js +9 -0
- package/dashboard/dist/assets/orgx-logo-Fm0FhtnV.png +0 -0
- package/dashboard/dist/assets/react-vendor-C2t2w4r2.js +32 -0
- package/dashboard/dist/assets/tanstack-C-KIc3Wc.js +1 -0
- package/dashboard/dist/assets/vendor-C-AHK0Ly.js +9 -0
- package/dashboard/dist/index.html +6 -2
- package/dist/agent-context-store.d.ts.map +1 -1
- package/dist/agent-context-store.js +21 -20
- package/dist/agent-context-store.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +21 -20
- package/dist/agent-run-store.js.map +1 -1
- package/dist/auth-store.d.ts.map +1 -1
- package/dist/auth-store.js +39 -44
- package/dist/auth-store.js.map +1 -1
- package/dist/byok-store.d.ts.map +1 -1
- package/dist/byok-store.js +24 -20
- package/dist/byok-store.js.map +1 -1
- package/dist/fs-utils.d.ts +7 -0
- package/dist/fs-utils.d.ts.map +1 -0
- package/dist/fs-utils.js +63 -0
- package/dist/fs-utils.js.map +1 -0
- package/dist/http-handler.d.ts +17 -0
- package/dist/http-handler.d.ts.map +1 -1
- package/dist/http-handler.js +281 -11
- package/dist/http-handler.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-apps/orgx-live.html +690 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +74 -29
- package/dist/outbox.js.map +1 -1
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +50 -0
- package/dist/paths.js.map +1 -0
- package/dist/reporting/outbox-replay.d.ts +2 -0
- package/dist/reporting/outbox-replay.d.ts.map +1 -0
- package/dist/reporting/outbox-replay.js +17 -0
- package/dist/reporting/outbox-replay.js.map +1 -0
- package/dist/reporting/rollups.d.ts +21 -0
- package/dist/reporting/rollups.d.ts.map +1 -0
- package/dist/reporting/rollups.js +85 -0
- package/dist/reporting/rollups.js.map +1 -0
- package/dist/snapshot-store.d.ts.map +1 -1
- package/dist/snapshot-store.js +24 -20
- package/dist/snapshot-store.js.map +1 -1
- package/package.json +2 -2
- package/dashboard/dist/assets/index-BjqNjHpY.css +0 -1
- package/dashboard/dist/assets/index-DCLkU4AM.js +0 -57
- package/dashboard/dist/assets/orgx-logo-QSE5QWy4.png +0 -0
package/dist/http-handler.js
CHANGED
|
@@ -27,6 +27,7 @@ import { defaultOutboxAdapter } from "./adapters/outbox.js";
|
|
|
27
27
|
import { readAgentContexts, upsertAgentContext } from "./agent-context-store.js";
|
|
28
28
|
import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from "./agent-run-store.js";
|
|
29
29
|
import { readByokKeys, writeByokKeys } from "./byok-store.js";
|
|
30
|
+
import { computeMilestoneRollup, computeWorkstreamRollup, } from "./reporting/rollups.js";
|
|
30
31
|
// =============================================================================
|
|
31
32
|
// Helpers
|
|
32
33
|
// =============================================================================
|
|
@@ -772,10 +773,14 @@ function resolveSafeDistPath(subPath) {
|
|
|
772
773
|
// =============================================================================
|
|
773
774
|
// Helpers
|
|
774
775
|
// =============================================================================
|
|
776
|
+
const IMMUTABLE_FILE_CACHE = new Map();
|
|
777
|
+
const IMMUTABLE_FILE_CACHE_MAX = 128;
|
|
775
778
|
function sendJson(res, status, data) {
|
|
776
779
|
const body = JSON.stringify(data);
|
|
777
780
|
res.writeHead(status, {
|
|
778
781
|
"Content-Type": "application/json; charset=utf-8",
|
|
782
|
+
// Avoid browser/proxy caching for live dashboards.
|
|
783
|
+
"Cache-Control": "no-store",
|
|
779
784
|
...SECURITY_HEADERS,
|
|
780
785
|
...CORS_HEADERS,
|
|
781
786
|
});
|
|
@@ -783,9 +788,32 @@ function sendJson(res, status, data) {
|
|
|
783
788
|
}
|
|
784
789
|
function sendFile(res, filePath, cacheControl) {
|
|
785
790
|
try {
|
|
791
|
+
const shouldCacheImmutable = cacheControl.includes("immutable");
|
|
792
|
+
if (shouldCacheImmutable) {
|
|
793
|
+
const cached = IMMUTABLE_FILE_CACHE.get(filePath);
|
|
794
|
+
if (cached) {
|
|
795
|
+
res.writeHead(200, {
|
|
796
|
+
"Content-Type": cached.contentType,
|
|
797
|
+
"Cache-Control": cacheControl,
|
|
798
|
+
...SECURITY_HEADERS,
|
|
799
|
+
...CORS_HEADERS,
|
|
800
|
+
});
|
|
801
|
+
res.end(cached.content);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
786
805
|
const content = readFileSync(filePath);
|
|
806
|
+
const type = contentType(filePath);
|
|
807
|
+
if (shouldCacheImmutable) {
|
|
808
|
+
if (IMMUTABLE_FILE_CACHE.size >= IMMUTABLE_FILE_CACHE_MAX) {
|
|
809
|
+
const firstKey = IMMUTABLE_FILE_CACHE.keys().next().value;
|
|
810
|
+
if (firstKey)
|
|
811
|
+
IMMUTABLE_FILE_CACHE.delete(firstKey);
|
|
812
|
+
}
|
|
813
|
+
IMMUTABLE_FILE_CACHE.set(filePath, { content, contentType: type });
|
|
814
|
+
}
|
|
787
815
|
res.writeHead(200, {
|
|
788
|
-
"Content-Type":
|
|
816
|
+
"Content-Type": type,
|
|
789
817
|
"Cache-Control": cacheControl,
|
|
790
818
|
...SECURITY_HEADERS,
|
|
791
819
|
...CORS_HEADERS,
|
|
@@ -1068,6 +1096,15 @@ function parseBooleanQuery(raw) {
|
|
|
1068
1096
|
normalized === "yes" ||
|
|
1069
1097
|
normalized === "on");
|
|
1070
1098
|
}
|
|
1099
|
+
function stableHash(value) {
|
|
1100
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1101
|
+
}
|
|
1102
|
+
function idempotencyKey(parts) {
|
|
1103
|
+
const raw = parts.filter((part) => typeof part === "string" && part.length > 0).join(":");
|
|
1104
|
+
const cleaned = raw.replace(/[^a-zA-Z0-9:_-]/g, "-").slice(0, 84);
|
|
1105
|
+
const suffix = stableHash(raw).slice(0, 20);
|
|
1106
|
+
return `${cleaned}:${suffix}`.slice(0, 120);
|
|
1107
|
+
}
|
|
1071
1108
|
const DEFAULT_DURATION_HOURS = {
|
|
1072
1109
|
initiative: 40,
|
|
1073
1110
|
workstream: 16,
|
|
@@ -1865,6 +1902,111 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
1865
1902
|
const dashboardEnabled = config.dashboardEnabled ??
|
|
1866
1903
|
true;
|
|
1867
1904
|
const outboxAdapter = adapters?.outbox ?? defaultOutboxAdapter;
|
|
1905
|
+
const openclawAdapter = adapters?.openclaw ?? {};
|
|
1906
|
+
const listAgents = openclawAdapter.listAgents ?? listOpenClawAgents;
|
|
1907
|
+
const spawnAgentTurn = openclawAdapter.spawnAgentTurn ?? spawnOpenClawAgentTurn;
|
|
1908
|
+
const stopProcess = openclawAdapter.stopDetachedProcess ?? stopDetachedProcess;
|
|
1909
|
+
const pidAlive = openclawAdapter.isPidAlive ?? isPidAlive;
|
|
1910
|
+
async function emitActivitySafe(input) {
|
|
1911
|
+
const initiativeId = input.initiativeId?.trim() ?? "";
|
|
1912
|
+
if (!initiativeId)
|
|
1913
|
+
return;
|
|
1914
|
+
const message = input.message.trim();
|
|
1915
|
+
if (!message)
|
|
1916
|
+
return;
|
|
1917
|
+
try {
|
|
1918
|
+
await client.emitActivity({
|
|
1919
|
+
initiative_id: initiativeId,
|
|
1920
|
+
run_id: input.runId ?? undefined,
|
|
1921
|
+
correlation_id: input.runId
|
|
1922
|
+
? undefined
|
|
1923
|
+
: (input.correlationId?.trim() || `openclaw-${Date.now()}`),
|
|
1924
|
+
source_client: "openclaw",
|
|
1925
|
+
message,
|
|
1926
|
+
phase: input.phase,
|
|
1927
|
+
progress_pct: typeof input.progressPct === "number" && Number.isFinite(input.progressPct)
|
|
1928
|
+
? Math.max(0, Math.min(100, Math.round(input.progressPct)))
|
|
1929
|
+
: undefined,
|
|
1930
|
+
level: input.level,
|
|
1931
|
+
next_step: input.nextStep,
|
|
1932
|
+
metadata: input.metadata,
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
catch {
|
|
1936
|
+
// best effort
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
async function syncParentRollupsForTask(input) {
|
|
1940
|
+
const initiativeId = input.initiativeId?.trim() ?? "";
|
|
1941
|
+
const taskId = input.taskId?.trim() ?? "";
|
|
1942
|
+
if (!initiativeId || !taskId)
|
|
1943
|
+
return;
|
|
1944
|
+
let tasks = [];
|
|
1945
|
+
try {
|
|
1946
|
+
const response = await client.listEntities("task", {
|
|
1947
|
+
initiative_id: initiativeId,
|
|
1948
|
+
limit: 4000,
|
|
1949
|
+
});
|
|
1950
|
+
tasks = Array.isArray(response?.data)
|
|
1951
|
+
? response.data
|
|
1952
|
+
: [];
|
|
1953
|
+
}
|
|
1954
|
+
catch {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const task = tasks.find((row) => String(row.id ?? "").trim() === taskId) ?? null;
|
|
1958
|
+
const resolvedMilestoneId = (input.milestoneId?.trim() || "") ||
|
|
1959
|
+
(task ? pickString(task, ["milestone_id", "milestoneId"]) ?? "" : "");
|
|
1960
|
+
const resolvedWorkstreamId = (input.workstreamId?.trim() || "") ||
|
|
1961
|
+
(task ? pickString(task, ["workstream_id", "workstreamId"]) ?? "" : "");
|
|
1962
|
+
if (resolvedMilestoneId) {
|
|
1963
|
+
const milestoneTaskStatuses = tasks
|
|
1964
|
+
.filter((row) => pickString(row, ["milestone_id", "milestoneId"]) === resolvedMilestoneId)
|
|
1965
|
+
.map((row) => pickString(row, ["status"]) ?? "todo");
|
|
1966
|
+
const rollup = computeMilestoneRollup(milestoneTaskStatuses);
|
|
1967
|
+
try {
|
|
1968
|
+
await client.applyChangeset({
|
|
1969
|
+
initiative_id: initiativeId,
|
|
1970
|
+
correlation_id: input.correlationId?.trim() || undefined,
|
|
1971
|
+
source_client: "openclaw",
|
|
1972
|
+
idempotency_key: idempotencyKey([
|
|
1973
|
+
"openclaw",
|
|
1974
|
+
"rollup",
|
|
1975
|
+
"milestone",
|
|
1976
|
+
resolvedMilestoneId,
|
|
1977
|
+
rollup.status,
|
|
1978
|
+
String(rollup.progressPct),
|
|
1979
|
+
String(rollup.done),
|
|
1980
|
+
String(rollup.total),
|
|
1981
|
+
]),
|
|
1982
|
+
operations: [
|
|
1983
|
+
{
|
|
1984
|
+
op: "milestone.update",
|
|
1985
|
+
milestone_id: resolvedMilestoneId,
|
|
1986
|
+
status: rollup.status,
|
|
1987
|
+
},
|
|
1988
|
+
],
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
catch {
|
|
1992
|
+
// best effort
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (resolvedWorkstreamId) {
|
|
1996
|
+
const workstreamTaskStatuses = tasks
|
|
1997
|
+
.filter((row) => pickString(row, ["workstream_id", "workstreamId"]) === resolvedWorkstreamId)
|
|
1998
|
+
.map((row) => pickString(row, ["status"]) ?? "todo");
|
|
1999
|
+
const rollup = computeWorkstreamRollup(workstreamTaskStatuses);
|
|
2000
|
+
try {
|
|
2001
|
+
await client.updateEntity("workstream", resolvedWorkstreamId, {
|
|
2002
|
+
status: rollup.status,
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
catch {
|
|
2006
|
+
// best effort
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1868
2010
|
const autoContinueRuns = new Map();
|
|
1869
2011
|
let autoContinueTickInFlight = false;
|
|
1870
2012
|
const AUTO_CONTINUE_TICK_MS = 2_500;
|
|
@@ -2075,7 +2217,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2075
2217
|
if (run.activeRunId) {
|
|
2076
2218
|
const record = getAgentRun(run.activeRunId);
|
|
2077
2219
|
const pid = record?.pid ?? null;
|
|
2078
|
-
if (pid &&
|
|
2220
|
+
if (pid && pidAlive(pid)) {
|
|
2079
2221
|
return;
|
|
2080
2222
|
}
|
|
2081
2223
|
// Run finished (or pid missing). Mark stopped and auto-complete the task.
|
|
@@ -2104,6 +2246,34 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2104
2246
|
run.lastError = safeErrorMessage(err);
|
|
2105
2247
|
}
|
|
2106
2248
|
}
|
|
2249
|
+
if (record.taskId) {
|
|
2250
|
+
await syncParentRollupsForTask({
|
|
2251
|
+
initiativeId: run.initiativeId,
|
|
2252
|
+
taskId: record.taskId,
|
|
2253
|
+
workstreamId: record.workstreamId,
|
|
2254
|
+
correlationId: record.runId,
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
await emitActivitySafe({
|
|
2258
|
+
initiativeId: run.initiativeId,
|
|
2259
|
+
correlationId: record.runId,
|
|
2260
|
+
phase: summary.hadError ? "blocked" : "completed",
|
|
2261
|
+
level: summary.hadError ? "warn" : "info",
|
|
2262
|
+
message: record.taskId
|
|
2263
|
+
? `Auto-continue ${summary.hadError ? "blocked" : "completed"} task ${record.taskId}.`
|
|
2264
|
+
: `Auto-continue run finished (${summary.hadError ? "blocked" : "completed"}).`,
|
|
2265
|
+
metadata: {
|
|
2266
|
+
event: "auto_continue_task_finished",
|
|
2267
|
+
agent_id: record.agentId,
|
|
2268
|
+
session_id: record.runId,
|
|
2269
|
+
task_id: record.taskId,
|
|
2270
|
+
workstream_id: record.workstreamId,
|
|
2271
|
+
tokens: summary.tokens,
|
|
2272
|
+
cost_usd: summary.costUsd,
|
|
2273
|
+
had_error: summary.hadError,
|
|
2274
|
+
error_message: summary.errorMessage,
|
|
2275
|
+
},
|
|
2276
|
+
});
|
|
2107
2277
|
run.lastRunId = record.runId;
|
|
2108
2278
|
run.lastTaskId = record.taskId ?? run.lastTaskId;
|
|
2109
2279
|
run.activeRunId = null;
|
|
@@ -2250,6 +2420,31 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2250
2420
|
});
|
|
2251
2421
|
return;
|
|
2252
2422
|
}
|
|
2423
|
+
await syncParentRollupsForTask({
|
|
2424
|
+
initiativeId: run.initiativeId,
|
|
2425
|
+
taskId: nextTaskNode.id,
|
|
2426
|
+
workstreamId: nextTaskNode.workstreamId,
|
|
2427
|
+
milestoneId: nextTaskNode.milestoneId,
|
|
2428
|
+
correlationId: sessionId,
|
|
2429
|
+
});
|
|
2430
|
+
await emitActivitySafe({
|
|
2431
|
+
initiativeId: run.initiativeId,
|
|
2432
|
+
correlationId: sessionId,
|
|
2433
|
+
phase: "execution",
|
|
2434
|
+
level: "info",
|
|
2435
|
+
message: `Auto-continue started task ${nextTaskNode.id}.`,
|
|
2436
|
+
metadata: {
|
|
2437
|
+
event: "auto_continue_task_started",
|
|
2438
|
+
agent_id: agentId,
|
|
2439
|
+
session_id: sessionId,
|
|
2440
|
+
task_id: nextTaskNode.id,
|
|
2441
|
+
task_title: nextTaskNode.title,
|
|
2442
|
+
workstream_id: nextTaskNode.workstreamId,
|
|
2443
|
+
workstream_title: workstreamTitle,
|
|
2444
|
+
milestone_id: nextTaskNode.milestoneId,
|
|
2445
|
+
milestone_title: milestoneTitle,
|
|
2446
|
+
},
|
|
2447
|
+
});
|
|
2253
2448
|
upsertAgentContext({
|
|
2254
2449
|
agentId,
|
|
2255
2450
|
initiativeId: run.initiativeId,
|
|
@@ -2257,7 +2452,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2257
2452
|
workstreamId: nextTaskNode.workstreamId,
|
|
2258
2453
|
taskId: nextTaskNode.id,
|
|
2259
2454
|
});
|
|
2260
|
-
const spawned =
|
|
2455
|
+
const spawned = spawnAgentTurn({
|
|
2261
2456
|
agentId,
|
|
2262
2457
|
sessionId,
|
|
2263
2458
|
message,
|
|
@@ -2553,7 +2748,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2553
2748
|
let requiresPremiumLaunch = Boolean(provider) || modelImpliesByok(requestedModel);
|
|
2554
2749
|
if (!requiresPremiumLaunch) {
|
|
2555
2750
|
try {
|
|
2556
|
-
const agents = await
|
|
2751
|
+
const agents = await listAgents();
|
|
2557
2752
|
const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === agentId) ??
|
|
2558
2753
|
null;
|
|
2559
2754
|
const agentModel = agentEntry && typeof agentEntry.model === "string"
|
|
@@ -2610,6 +2805,46 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2610
2805
|
});
|
|
2611
2806
|
return true;
|
|
2612
2807
|
}
|
|
2808
|
+
if (initiativeId) {
|
|
2809
|
+
try {
|
|
2810
|
+
await client.updateEntity("initiative", initiativeId, { status: "active" });
|
|
2811
|
+
}
|
|
2812
|
+
catch {
|
|
2813
|
+
// best effort
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
if (taskId) {
|
|
2817
|
+
try {
|
|
2818
|
+
await client.updateEntity("task", taskId, { status: "in_progress" });
|
|
2819
|
+
}
|
|
2820
|
+
catch {
|
|
2821
|
+
// best effort
|
|
2822
|
+
}
|
|
2823
|
+
await syncParentRollupsForTask({
|
|
2824
|
+
initiativeId,
|
|
2825
|
+
taskId,
|
|
2826
|
+
workstreamId,
|
|
2827
|
+
correlationId: sessionId,
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
await emitActivitySafe({
|
|
2831
|
+
initiativeId,
|
|
2832
|
+
correlationId: sessionId,
|
|
2833
|
+
phase: "execution",
|
|
2834
|
+
message: taskId
|
|
2835
|
+
? `Launched agent ${agentId} for task ${taskId}.`
|
|
2836
|
+
: `Launched agent ${agentId}.`,
|
|
2837
|
+
level: "info",
|
|
2838
|
+
metadata: {
|
|
2839
|
+
event: "agent_launch",
|
|
2840
|
+
agent_id: agentId,
|
|
2841
|
+
session_id: sessionId,
|
|
2842
|
+
workstream_id: workstreamId,
|
|
2843
|
+
task_id: taskId,
|
|
2844
|
+
provider,
|
|
2845
|
+
model: requestedModel,
|
|
2846
|
+
},
|
|
2847
|
+
});
|
|
2613
2848
|
let routedProvider = null;
|
|
2614
2849
|
let routedModel = null;
|
|
2615
2850
|
if (provider) {
|
|
@@ -2628,7 +2863,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2628
2863
|
workstreamId,
|
|
2629
2864
|
taskId,
|
|
2630
2865
|
});
|
|
2631
|
-
const spawned =
|
|
2866
|
+
const spawned = spawnAgentTurn({
|
|
2632
2867
|
agentId,
|
|
2633
2868
|
sessionId,
|
|
2634
2869
|
message,
|
|
@@ -2692,7 +2927,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2692
2927
|
sendJson(res, 409, { ok: false, error: "Run has no tracked pid" });
|
|
2693
2928
|
return true;
|
|
2694
2929
|
}
|
|
2695
|
-
const result = await
|
|
2930
|
+
const result = await stopProcess(record.pid);
|
|
2696
2931
|
const updated = markAgentRunStopped(runId);
|
|
2697
2932
|
sendJson(res, 200, {
|
|
2698
2933
|
ok: true,
|
|
@@ -2752,7 +2987,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2752
2987
|
modelImpliesByok(record.model ?? null);
|
|
2753
2988
|
if (!requiresPremiumRestart) {
|
|
2754
2989
|
try {
|
|
2755
|
-
const agents = await
|
|
2990
|
+
const agents = await listAgents();
|
|
2756
2991
|
const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === record.agentId) ?? null;
|
|
2757
2992
|
const agentModel = agentEntry && typeof agentEntry.model === "string"
|
|
2758
2993
|
? agentEntry.model
|
|
@@ -2802,7 +3037,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2802
3037
|
workstreamId: record.workstreamId,
|
|
2803
3038
|
taskId: record.taskId,
|
|
2804
3039
|
});
|
|
2805
|
-
const spawned =
|
|
3040
|
+
const spawned = spawnAgentTurn({
|
|
2806
3041
|
agentId: record.agentId,
|
|
2807
3042
|
sessionId,
|
|
2808
3043
|
message,
|
|
@@ -2863,7 +3098,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2863
3098
|
}
|
|
2864
3099
|
let requiresPremiumAutoContinue = false;
|
|
2865
3100
|
try {
|
|
2866
|
-
const agents = await
|
|
3101
|
+
const agents = await listAgents();
|
|
2867
3102
|
const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === agentId) ??
|
|
2868
3103
|
null;
|
|
2869
3104
|
const agentModel = agentEntry && typeof agentEntry.model === "string"
|
|
@@ -3338,7 +3573,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3338
3573
|
case "agents/catalog": {
|
|
3339
3574
|
try {
|
|
3340
3575
|
const [openclawAgents, localSnapshot] = await Promise.all([
|
|
3341
|
-
|
|
3576
|
+
listAgents(),
|
|
3342
3577
|
loadLocalOpenClawSnapshot(240).catch(() => null),
|
|
3343
3578
|
]);
|
|
3344
3579
|
const localById = new Map();
|
|
@@ -3612,7 +3847,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3612
3847
|
agentId = agentId.trim();
|
|
3613
3848
|
if (!agentId) {
|
|
3614
3849
|
try {
|
|
3615
|
-
const agents = await
|
|
3850
|
+
const agents = await listAgents();
|
|
3616
3851
|
const defaultAgent = agents.find((entry) => Boolean(entry.isDefault)) ?? agents[0] ?? null;
|
|
3617
3852
|
const candidate = defaultAgent && typeof defaultAgent.id === "string" ? defaultAgent.id.trim() : "";
|
|
3618
3853
|
if (candidate)
|
|
@@ -4313,17 +4548,26 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4313
4548
|
let closed = false;
|
|
4314
4549
|
let streamOpened = false;
|
|
4315
4550
|
let idleTimer = null;
|
|
4551
|
+
let heartbeatTimer = null;
|
|
4552
|
+
let heartbeatBackpressure = false;
|
|
4316
4553
|
const clearIdleTimer = () => {
|
|
4317
4554
|
if (idleTimer) {
|
|
4318
4555
|
clearTimeout(idleTimer);
|
|
4319
4556
|
idleTimer = null;
|
|
4320
4557
|
}
|
|
4321
4558
|
};
|
|
4559
|
+
const clearHeartbeatTimer = () => {
|
|
4560
|
+
if (heartbeatTimer) {
|
|
4561
|
+
clearInterval(heartbeatTimer);
|
|
4562
|
+
heartbeatTimer = null;
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
4322
4565
|
const closeStream = () => {
|
|
4323
4566
|
if (closed)
|
|
4324
4567
|
return;
|
|
4325
4568
|
closed = true;
|
|
4326
4569
|
clearIdleTimer();
|
|
4570
|
+
clearHeartbeatTimer();
|
|
4327
4571
|
streamAbortController.abort();
|
|
4328
4572
|
if (reader) {
|
|
4329
4573
|
void reader.cancel().catch(() => undefined);
|
|
@@ -4373,6 +4617,32 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4373
4617
|
...CORS_HEADERS,
|
|
4374
4618
|
});
|
|
4375
4619
|
streamOpened = true;
|
|
4620
|
+
// Heartbeat comments keep intermediary proxies from timing out idle SSE.
|
|
4621
|
+
// They also prevent the dashboard from flickering into reconnect mode
|
|
4622
|
+
// during long quiet periods.
|
|
4623
|
+
heartbeatTimer = setInterval(() => {
|
|
4624
|
+
if (closed || heartbeatBackpressure)
|
|
4625
|
+
return;
|
|
4626
|
+
try {
|
|
4627
|
+
// Keepalive comment line (single newline to avoid terminating an upstream event mid-chunk).
|
|
4628
|
+
const accepted = write(Buffer.from(`: ping ${Date.now()}\n`, "utf8"));
|
|
4629
|
+
resetIdleTimer();
|
|
4630
|
+
if (accepted === false) {
|
|
4631
|
+
heartbeatBackpressure = true;
|
|
4632
|
+
if (typeof res.once === "function") {
|
|
4633
|
+
res.once("drain", () => {
|
|
4634
|
+
heartbeatBackpressure = false;
|
|
4635
|
+
if (!closed)
|
|
4636
|
+
resetIdleTimer();
|
|
4637
|
+
});
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
catch {
|
|
4642
|
+
closeStream();
|
|
4643
|
+
}
|
|
4644
|
+
}, 20_000);
|
|
4645
|
+
heartbeatTimer.unref?.();
|
|
4376
4646
|
if (!upstream.body) {
|
|
4377
4647
|
closeStream();
|
|
4378
4648
|
return true;
|