@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.
Files changed (56) hide show
  1. package/dashboard/dist/assets/MissionControlView-CthHdl6R.js +1 -0
  2. package/dashboard/dist/assets/SessionInspector-Dq0Z5WMo.js +1 -0
  3. package/dashboard/dist/assets/index-CoLgC4zE.js +11 -0
  4. package/dashboard/dist/assets/index-jfEYE0kO.css +1 -0
  5. package/dashboard/dist/assets/motion-CVDprFZg.js +9 -0
  6. package/dashboard/dist/assets/orgx-logo-Fm0FhtnV.png +0 -0
  7. package/dashboard/dist/assets/react-vendor-C2t2w4r2.js +32 -0
  8. package/dashboard/dist/assets/tanstack-C-KIc3Wc.js +1 -0
  9. package/dashboard/dist/assets/vendor-C-AHK0Ly.js +9 -0
  10. package/dashboard/dist/index.html +6 -2
  11. package/dist/agent-context-store.d.ts.map +1 -1
  12. package/dist/agent-context-store.js +21 -20
  13. package/dist/agent-context-store.js.map +1 -1
  14. package/dist/agent-run-store.d.ts.map +1 -1
  15. package/dist/agent-run-store.js +21 -20
  16. package/dist/agent-run-store.js.map +1 -1
  17. package/dist/auth-store.d.ts.map +1 -1
  18. package/dist/auth-store.js +39 -44
  19. package/dist/auth-store.js.map +1 -1
  20. package/dist/byok-store.d.ts.map +1 -1
  21. package/dist/byok-store.js +24 -20
  22. package/dist/byok-store.js.map +1 -1
  23. package/dist/fs-utils.d.ts +7 -0
  24. package/dist/fs-utils.d.ts.map +1 -0
  25. package/dist/fs-utils.js +63 -0
  26. package/dist/fs-utils.js.map +1 -0
  27. package/dist/http-handler.d.ts +17 -0
  28. package/dist/http-handler.d.ts.map +1 -1
  29. package/dist/http-handler.js +281 -11
  30. package/dist/http-handler.js.map +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/mcp-apps/orgx-live.html +690 -0
  35. package/dist/outbox.d.ts.map +1 -1
  36. package/dist/outbox.js +74 -29
  37. package/dist/outbox.js.map +1 -1
  38. package/dist/paths.d.ts +23 -0
  39. package/dist/paths.d.ts.map +1 -0
  40. package/dist/paths.js +50 -0
  41. package/dist/paths.js.map +1 -0
  42. package/dist/reporting/outbox-replay.d.ts +2 -0
  43. package/dist/reporting/outbox-replay.d.ts.map +1 -0
  44. package/dist/reporting/outbox-replay.js +17 -0
  45. package/dist/reporting/outbox-replay.js.map +1 -0
  46. package/dist/reporting/rollups.d.ts +21 -0
  47. package/dist/reporting/rollups.d.ts.map +1 -0
  48. package/dist/reporting/rollups.js +85 -0
  49. package/dist/reporting/rollups.js.map +1 -0
  50. package/dist/snapshot-store.d.ts.map +1 -1
  51. package/dist/snapshot-store.js +24 -20
  52. package/dist/snapshot-store.js.map +1 -1
  53. package/package.json +2 -2
  54. package/dashboard/dist/assets/index-BjqNjHpY.css +0 -1
  55. package/dashboard/dist/assets/index-DCLkU4AM.js +0 -57
  56. package/dashboard/dist/assets/orgx-logo-QSE5QWy4.png +0 -0
@@ -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": contentType(filePath),
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 && isPidAlive(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 = spawnOpenClawAgentTurn({
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 listOpenClawAgents();
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 = spawnOpenClawAgentTurn({
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 stopDetachedProcess(record.pid);
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 listOpenClawAgents();
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 = spawnOpenClawAgentTurn({
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 listOpenClawAgents();
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
- listOpenClawAgents(),
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 listOpenClawAgents();
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;