@useorgx/openclaw-plugin 0.4.6 → 0.4.9

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 (137) hide show
  1. package/README.md +310 -24
  2. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  3. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  4. package/dashboard/dist/assets/iFdvE7lx.js +1 -0
  5. package/dashboard/dist/assets/jRJsmpYM.js +1 -0
  6. package/dashboard/dist/index.html +2 -2
  7. package/dist/activity-actor-fields.d.ts +3 -0
  8. package/dist/activity-actor-fields.js +128 -0
  9. package/dist/activity-store.js +12 -19
  10. package/dist/agent-context-store.js +5 -25
  11. package/dist/agent-run-store.js +5 -25
  12. package/dist/agent-suite.js +1 -8
  13. package/dist/artifacts/register-artifact.d.ts +47 -0
  14. package/dist/artifacts/register-artifact.js +271 -0
  15. package/dist/auth/flows.d.ts +47 -0
  16. package/dist/auth/flows.js +169 -0
  17. package/dist/auth-store.js +14 -39
  18. package/dist/byok-store.js +5 -19
  19. package/dist/cli/orgx.d.ts +66 -0
  20. package/dist/cli/orgx.js +91 -0
  21. package/dist/config/refresh.d.ts +32 -0
  22. package/dist/config/refresh.js +55 -0
  23. package/dist/config/resolution.d.ts +37 -0
  24. package/dist/config/resolution.js +178 -0
  25. package/dist/contracts/client.d.ts +1 -0
  26. package/dist/contracts/client.js +7 -5
  27. package/dist/contracts/shared-types.d.ts +147 -0
  28. package/dist/contracts/shared-types.js +3 -0
  29. package/dist/contracts/types.d.ts +1 -130
  30. package/dist/contracts/types.js +5 -0
  31. package/dist/entities/auto-assignment.d.ts +36 -0
  32. package/dist/entities/auto-assignment.js +115 -0
  33. package/dist/entity-comment-store.js +5 -25
  34. package/dist/hash-utils.d.ts +2 -0
  35. package/dist/hash-utils.js +12 -0
  36. package/dist/http/helpers/activity-headline.d.ts +10 -0
  37. package/dist/http/helpers/activity-headline.js +192 -0
  38. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  39. package/dist/http/helpers/artifact-fallback.js +148 -0
  40. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  41. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  42. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  43. package/dist/http/helpers/autopilot-operations.js +403 -0
  44. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  45. package/dist/http/helpers/autopilot-runtime.js +319 -0
  46. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  47. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  48. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  49. package/dist/http/helpers/decision-mapper.js +44 -0
  50. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  51. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  52. package/dist/http/helpers/hash-utils.d.ts +1 -0
  53. package/dist/http/helpers/hash-utils.js +1 -0
  54. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  55. package/dist/http/helpers/kickoff-context.js +154 -0
  56. package/dist/http/helpers/mission-control.d.ts +94 -0
  57. package/dist/http/helpers/mission-control.js +894 -0
  58. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  59. package/dist/http/helpers/openclaw-cli.js +283 -0
  60. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  61. package/dist/http/helpers/runtime-sse.js +110 -0
  62. package/dist/http/helpers/value-utils.d.ts +6 -0
  63. package/dist/http/helpers/value-utils.js +67 -0
  64. package/dist/http/index.d.ts +88 -0
  65. package/dist/http/index.js +2353 -0
  66. package/dist/http/router.d.ts +23 -0
  67. package/dist/http/router.js +23 -0
  68. package/dist/http/routes/agent-control.d.ts +79 -0
  69. package/dist/http/routes/agent-control.js +684 -0
  70. package/dist/http/routes/agent-suite.d.ts +29 -0
  71. package/dist/http/routes/agent-suite.js +198 -0
  72. package/dist/http/routes/agents-catalog.d.ts +40 -0
  73. package/dist/http/routes/agents-catalog.js +83 -0
  74. package/dist/http/routes/billing.d.ts +23 -0
  75. package/dist/http/routes/billing.js +55 -0
  76. package/dist/http/routes/debug.d.ts +14 -0
  77. package/dist/http/routes/debug.js +21 -0
  78. package/dist/http/routes/decision-actions.d.ts +13 -0
  79. package/dist/http/routes/decision-actions.js +66 -0
  80. package/dist/http/routes/delegation.d.ts +19 -0
  81. package/dist/http/routes/delegation.js +32 -0
  82. package/dist/http/routes/entities.d.ts +47 -0
  83. package/dist/http/routes/entities.js +152 -0
  84. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  85. package/dist/http/routes/entity-dynamic.js +191 -0
  86. package/dist/http/routes/health.d.ts +22 -0
  87. package/dist/http/routes/health.js +49 -0
  88. package/dist/http/routes/live-legacy.d.ts +110 -0
  89. package/dist/http/routes/live-legacy.js +598 -0
  90. package/dist/http/routes/live-misc.d.ts +69 -0
  91. package/dist/http/routes/live-misc.js +206 -0
  92. package/dist/http/routes/live-snapshot.d.ts +90 -0
  93. package/dist/http/routes/live-snapshot.js +297 -0
  94. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  95. package/dist/http/routes/mission-control-actions.js +541 -0
  96. package/dist/http/routes/mission-control-read.d.ts +28 -0
  97. package/dist/http/routes/mission-control-read.js +67 -0
  98. package/dist/http/routes/onboarding.d.ts +34 -0
  99. package/dist/http/routes/onboarding.js +101 -0
  100. package/dist/http/routes/run-control.d.ts +24 -0
  101. package/dist/http/routes/run-control.js +86 -0
  102. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  103. package/dist/http/routes/runtime-hooks.js +437 -0
  104. package/dist/http/routes/settings-byok.d.ts +23 -0
  105. package/dist/http/routes/settings-byok.js +163 -0
  106. package/dist/http/routes/summary.d.ts +18 -0
  107. package/dist/http/routes/summary.js +42 -0
  108. package/dist/http/routes/work-artifacts.d.ts +9 -0
  109. package/dist/http/routes/work-artifacts.js +36 -0
  110. package/dist/http/shared-state.d.ts +16 -0
  111. package/dist/http/shared-state.js +1 -0
  112. package/dist/http-handler.d.ts +1 -88
  113. package/dist/http-handler.js +1 -9664
  114. package/dist/index.js +122 -2121
  115. package/dist/json-utils.d.ts +1 -0
  116. package/dist/json-utils.js +8 -0
  117. package/dist/local-openclaw.js +8 -0
  118. package/dist/mcp-client-setup.js +75 -90
  119. package/dist/next-up-queue-store.js +4 -18
  120. package/dist/runtime-instance-store.js +8 -34
  121. package/dist/services/background.d.ts +23 -0
  122. package/dist/services/background.js +23 -0
  123. package/dist/services/instrumentation.d.ts +29 -0
  124. package/dist/services/instrumentation.js +136 -0
  125. package/dist/snapshot-store.js +5 -25
  126. package/dist/stores/json-store.d.ts +11 -0
  127. package/dist/stores/json-store.js +42 -0
  128. package/dist/sync/outbox-replay.d.ts +55 -0
  129. package/dist/sync/outbox-replay.js +514 -0
  130. package/dist/tools/core-tools.d.ts +76 -0
  131. package/dist/tools/core-tools.js +1005 -0
  132. package/dist/worker-supervisor.js +15 -0
  133. package/package.json +6 -1
  134. package/dashboard/dist/assets/0tOC3wSN.js +0 -214
  135. package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
  136. package/dashboard/dist/assets/CyxZio4Y.js +0 -1
  137. package/dashboard/dist/assets/DaAIOik3.css +0 -1
@@ -0,0 +1,598 @@
1
+ function toContextBundle(value) {
2
+ return {
3
+ agents: value.agents ?? {},
4
+ runs: value.runs ?? {},
5
+ };
6
+ }
7
+ export function registerLiveLegacyRoutes(router, deps) {
8
+ async function renderLiveSessions(query, res) {
9
+ try {
10
+ const initiative = query.get("initiative");
11
+ const limit = query.get("limit") ? Number(query.get("limit")) : undefined;
12
+ let data = await deps.getLiveSessions({
13
+ initiative,
14
+ limit: Number.isFinite(limit) ? limit : undefined,
15
+ });
16
+ const runtimeInstances = initiative && initiative.trim().length > 0
17
+ ? deps
18
+ .listRuntimeInstances({ limit: 320 })
19
+ .filter((instance) => instance.initiativeId === initiative)
20
+ : deps.listRuntimeInstances({ limit: 320 });
21
+ data = deps.injectRuntimeInstancesAsSessions(data, runtimeInstances);
22
+ data = deps.enrichSessionsWithRuntime(data, runtimeInstances);
23
+ deps.sendJson(res, 200, data);
24
+ }
25
+ catch (err) {
26
+ try {
27
+ const initiative = query.get("initiative");
28
+ const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
29
+ const limit = Number.isFinite(limitRaw) ? Math.max(1, Number(limitRaw)) : 100;
30
+ let local = deps.toLocalSessionTree(await deps.loadLocalOpenClawSnapshot(Math.max(limit, 200)), limit);
31
+ local = deps.applyAgentContextsToSessionTree(local, toContextBundle(deps.readAgentContexts()));
32
+ if (initiative && initiative.trim().length > 0) {
33
+ const filteredNodes = local.nodes.filter((node) => node.initiativeId === initiative || node.groupId === initiative);
34
+ const filteredIds = new Set(filteredNodes.map((node) => node.id));
35
+ const filteredGroupIds = new Set(filteredNodes.map((node) => node.groupId));
36
+ local = {
37
+ nodes: filteredNodes,
38
+ edges: local.edges.filter((edge) => filteredIds.has(edge.parentId) && filteredIds.has(edge.childId)),
39
+ groups: local.groups.filter((group) => filteredGroupIds.has(group.id)),
40
+ };
41
+ }
42
+ deps.sendJson(res, 200, local);
43
+ }
44
+ catch (localErr) {
45
+ deps.sendJson(res, 500, {
46
+ error: deps.safeErrorMessage(err),
47
+ localFallbackError: deps.safeErrorMessage(localErr),
48
+ });
49
+ }
50
+ }
51
+ }
52
+ router.add("GET", "live/sessions", async ({ query, res }) => renderLiveSessions(query, res), "Legacy live sessions endpoint");
53
+ router.add("HEAD", "live/sessions", async ({ query, res }) => renderLiveSessions(query, res), "Legacy live sessions endpoint (HEAD)");
54
+ async function renderLiveActivityPage(query, res) {
55
+ const run = query.get("run");
56
+ const since = query.get("since");
57
+ const until = query.get("until");
58
+ const cursor = query.get("cursor");
59
+ const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
60
+ const limit = Number.isFinite(limitRaw)
61
+ ? Math.max(1, Math.floor(Number(limitRaw)))
62
+ : 200;
63
+ let page = deps.listActivityPage({
64
+ limit,
65
+ runId: run,
66
+ since,
67
+ until,
68
+ cursor,
69
+ });
70
+ {
71
+ const ctx = toContextBundle(deps.readAgentContexts());
72
+ page = {
73
+ ...page,
74
+ activities: deps.applyAgentContextsToActivity(page.activities, ctx),
75
+ };
76
+ }
77
+ const warmKey = `${run ?? ""}::${since ?? ""}::${until ?? ""}`;
78
+ const lastWarmAt = deps.activityWarmByKey.get(warmKey) ?? 0;
79
+ const shouldWarm = Date.now() - lastWarmAt > deps.activityWarmThrottleMs &&
80
+ (cursor === null || cursor === "" || page.activities.length < limit);
81
+ if (shouldWarm) {
82
+ deps.activityWarmByKey.set(warmKey, Date.now());
83
+ try {
84
+ const warmLimit = Math.max(800, Math.min(6_000, limit * 10));
85
+ const data = await deps.getLiveActivity({
86
+ run,
87
+ since,
88
+ limit: warmLimit,
89
+ });
90
+ const remote = Array.isArray(data.activities) ? data.activities : [];
91
+ {
92
+ const ctx = toContextBundle(deps.readAgentContexts());
93
+ const withContexts = deps.applyAgentContextsToActivity(remote, ctx);
94
+ deps.appendActivityItems(withContexts);
95
+ }
96
+ page = deps.listActivityPage({
97
+ limit,
98
+ runId: run,
99
+ since,
100
+ until,
101
+ cursor,
102
+ });
103
+ {
104
+ const ctx = toContextBundle(deps.readAgentContexts());
105
+ page = {
106
+ ...page,
107
+ activities: deps.applyAgentContextsToActivity(page.activities, ctx),
108
+ };
109
+ }
110
+ }
111
+ catch {
112
+ // best effort
113
+ }
114
+ }
115
+ deps.sendJson(res, 200, page);
116
+ }
117
+ router.add("GET", "live/activity/page", async ({ query, res }) => renderLiveActivityPage(query, res), "Paginated live activity");
118
+ router.add("HEAD", "live/activity/page", async ({ query, res }) => renderLiveActivityPage(query, res), "Paginated live activity (HEAD)");
119
+ async function renderLiveActivity(query, res) {
120
+ try {
121
+ const run = query.get("run");
122
+ const limit = query.get("limit") ? Number(query.get("limit")) : undefined;
123
+ const since = query.get("since");
124
+ const data = await deps.getLiveActivity({
125
+ run,
126
+ since,
127
+ limit: Number.isFinite(limit) ? limit : undefined,
128
+ });
129
+ let activities = Array.isArray(data.activities) ? data.activities : [];
130
+ let total = typeof data.total === "number" && Number.isFinite(data.total)
131
+ ? Number(data.total)
132
+ : activities.length;
133
+ try {
134
+ const buffered = await deps.outboxReadAllItems();
135
+ if (buffered.length > 0) {
136
+ let merged = [...activities, ...buffered];
137
+ if (run && run.trim().length > 0) {
138
+ merged = merged.filter((item) => item.runId === run);
139
+ }
140
+ if (since && since.trim().length > 0) {
141
+ const sinceEpoch = Date.parse(since);
142
+ if (Number.isFinite(sinceEpoch)) {
143
+ merged = merged.filter((item) => Date.parse(item.timestamp) >= sinceEpoch);
144
+ }
145
+ }
146
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
147
+ const deduped = [];
148
+ const seen = new Set();
149
+ for (const item of merged) {
150
+ if (seen.has(item.id))
151
+ continue;
152
+ seen.add(item.id);
153
+ deduped.push(item);
154
+ }
155
+ total = deduped.length;
156
+ if (Number.isFinite(limit)) {
157
+ const cap = Math.max(1, Math.floor(Number(limit)));
158
+ activities = deduped.slice(0, cap);
159
+ }
160
+ else {
161
+ activities = deduped;
162
+ }
163
+ }
164
+ }
165
+ catch {
166
+ // best effort
167
+ }
168
+ try {
169
+ const ctx = toContextBundle(deps.readAgentContexts());
170
+ const withContexts = deps.applyAgentContextsToActivity(activities, ctx);
171
+ if (withContexts.length > 0)
172
+ deps.appendActivityItems(withContexts);
173
+ deps.sendJson(res, 200, { ...data, activities: withContexts, total });
174
+ }
175
+ catch {
176
+ deps.sendJson(res, 200, { ...data, activities, total });
177
+ }
178
+ }
179
+ catch (err) {
180
+ try {
181
+ const run = query.get("run");
182
+ const limitRaw = query.get("limit") ? Number(query.get("limit")) : undefined;
183
+ const since = query.get("since");
184
+ const limit = Number.isFinite(limitRaw) ? Math.max(1, Number(limitRaw)) : 240;
185
+ const localSnapshot = await deps.loadLocalOpenClawSnapshot(Math.max(limit, 240));
186
+ let local = await deps.toLocalLiveActivity(localSnapshot, Math.max(limit, 240));
187
+ if (run && run.trim().length > 0) {
188
+ local = {
189
+ activities: local.activities.filter((item) => item.runId === run),
190
+ total: local.activities.filter((item) => item.runId === run).length,
191
+ };
192
+ }
193
+ if (since && since.trim().length > 0) {
194
+ const sinceEpoch = Date.parse(since);
195
+ if (Number.isFinite(sinceEpoch)) {
196
+ const filtered = local.activities.filter((item) => Date.parse(item.timestamp) >= sinceEpoch);
197
+ local = {
198
+ activities: filtered,
199
+ total: filtered.length,
200
+ };
201
+ }
202
+ }
203
+ const ctx = toContextBundle(deps.readAgentContexts());
204
+ const activitiesWithContexts = deps.applyAgentContextsToActivity(local.activities, ctx);
205
+ let merged = activitiesWithContexts;
206
+ try {
207
+ const buffered = await deps.outboxReadAllItems();
208
+ if (buffered.length > 0) {
209
+ const byId = new Map();
210
+ for (const item of [...merged, ...buffered]) {
211
+ if (!item || typeof item.id !== "string")
212
+ continue;
213
+ byId.set(item.id, item);
214
+ }
215
+ merged = Array.from(byId.values()).sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
216
+ }
217
+ }
218
+ catch {
219
+ // best effort
220
+ }
221
+ try {
222
+ deps.appendActivityItems(merged);
223
+ }
224
+ catch {
225
+ // best effort
226
+ }
227
+ deps.sendJson(res, 200, {
228
+ activities: merged.slice(0, limit),
229
+ total: merged.length,
230
+ });
231
+ }
232
+ catch (localErr) {
233
+ deps.sendJson(res, 500, {
234
+ error: deps.safeErrorMessage(err),
235
+ localFallbackError: deps.safeErrorMessage(localErr),
236
+ });
237
+ }
238
+ }
239
+ }
240
+ router.add("GET", "live/activity", async ({ query, res }) => renderLiveActivity(query, res), "Legacy live activity endpoint");
241
+ router.add("HEAD", "live/activity", async ({ query, res }) => renderLiveActivity(query, res), "Legacy live activity endpoint (HEAD)");
242
+ async function renderLiveActivityDetail(query, res) {
243
+ const turnId = query.get("turnId") ?? query.get("turn_id");
244
+ const sessionKey = query.get("sessionKey") ?? query.get("session_key");
245
+ const run = query.get("run");
246
+ if (!turnId || turnId.trim().length === 0) {
247
+ deps.sendJson(res, 400, { error: "turnId is required" });
248
+ return;
249
+ }
250
+ try {
251
+ const detail = await deps.loadLocalTurnDetail({
252
+ turnId,
253
+ sessionKey,
254
+ runId: run,
255
+ });
256
+ if (!detail) {
257
+ deps.sendJson(res, 404, {
258
+ error: "Turn detail unavailable",
259
+ turnId,
260
+ });
261
+ return;
262
+ }
263
+ deps.sendJson(res, 200, { detail });
264
+ }
265
+ catch (err) {
266
+ deps.sendJson(res, 500, { error: deps.safeErrorMessage(err), turnId });
267
+ }
268
+ }
269
+ router.add("GET", "live/activity/detail", async ({ query, res }) => renderLiveActivityDetail(query, res), "Detailed activity turn view");
270
+ router.add("HEAD", "live/activity/detail", async ({ query, res }) => renderLiveActivityDetail(query, res), "Detailed activity turn view (HEAD)");
271
+ router.add("GET", "live/filesystem/open", ({ query, res }) => {
272
+ const rawPath = query.get("path") ?? "";
273
+ if (!rawPath.trim()) {
274
+ deps.sendJson(res, 400, { error: "path is required" });
275
+ return;
276
+ }
277
+ const pathInput = rawPath.trim();
278
+ if (/^https?:\/\//i.test(pathInput)) {
279
+ res.writeHead(302, {
280
+ Location: pathInput,
281
+ ...deps.securityHeaders,
282
+ ...deps.corsHeaders,
283
+ });
284
+ res.end();
285
+ return;
286
+ }
287
+ const resolvedPath = deps.resolveFilesystemOpenPath(pathInput);
288
+ const escapedInput = deps.escapeHtml(pathInput);
289
+ const escapedResolved = deps.escapeHtml(resolvedPath);
290
+ const shellPath = resolvedPath.replaceAll("'", "'\\''");
291
+ if (!deps.existsSync(resolvedPath)) {
292
+ deps.sendHtml(res, 404, `<!doctype html><html><head><meta charset="utf-8"/><title>Path Not Found</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}a{color:#BFFF00}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Path not found</h1><p style="margin:0 0 12px;color:#9ca3af;">The evidence path no longer exists.</p><pre>${escapedInput}</pre><p style="margin:12px 0 0;color:#9ca3af;">Resolved as:</p><pre>${escapedResolved}</pre></body></html>`);
293
+ return;
294
+ }
295
+ try {
296
+ const stats = deps.statSync(resolvedPath);
297
+ if (stats.isDirectory()) {
298
+ const entries = deps.readdirSync(resolvedPath);
299
+ const visibleEntries = entries.slice(0, deps.filePreviewMaxDirEntries);
300
+ const items = visibleEntries
301
+ .map((name) => {
302
+ const nextPath = deps.resolvePath(resolvedPath, name);
303
+ const href = `/orgx/api/live/filesystem/open?path=${encodeURIComponent(nextPath)}`;
304
+ return `<li style="margin:0 0 6px;"><a href="${href}" target="_blank" rel="noreferrer" style="color:#BFFF00;text-decoration:none;">${deps.escapeHtml(name)}</a></li>`;
305
+ })
306
+ .join("");
307
+ const overflowNote = entries.length > visibleEntries.length
308
+ ? `<p style="margin:12px 0 0;color:#9ca3af;">Showing ${visibleEntries.length} of ${entries.length} entries.</p>`
309
+ : "";
310
+ deps.sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Directory Preview</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre,ul{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px}ul{list-style:none;margin:0;max-height:70vh;overflow:auto}pre{white-space:pre-wrap;word-break:break-word}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Directory</h1><p style="margin:0 0 12px;color:#9ca3af;">${escapedResolved}</p><ul>${items || "<li style=\"color:#9ca3af;\">(empty)</li>"}</ul>${overflowNote}<p style="margin:12px 0 0;color:#9ca3af;">Tip: open in terminal with <code>ls -la '${deps.escapeHtml(shellPath)}'</code></p></body></html>`);
311
+ return;
312
+ }
313
+ if (!stats.isFile()) {
314
+ deps.sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Unsupported Path</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Unsupported path type</h1><p style="margin:0 0 12px;color:#9ca3af;">Only files and directories are previewable.</p><pre>${escapedResolved}</pre></body></html>`);
315
+ return;
316
+ }
317
+ const totalBytes = Number.isFinite(stats.size) ? Math.max(0, stats.size) : 0;
318
+ const { previewBuffer, truncated } = deps.readFilePreview(resolvedPath, totalBytes);
319
+ const isBinary = previewBuffer.includes(0);
320
+ const sizeLabel = `${totalBytes.toLocaleString()} bytes`;
321
+ if (isBinary) {
322
+ deps.sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Binary File</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Binary file</h1><p style="margin:0 0 12px;color:#9ca3af;">Cannot render binary content in browser preview.</p><pre>${escapedResolved}\n${deps.escapeHtml(sizeLabel)}</pre><p style="margin:12px 0 0;color:#9ca3af;">Inspect in terminal with <code>file '${deps.escapeHtml(shellPath)}'</code></p></body></html>`);
323
+ return;
324
+ }
325
+ const previewText = previewBuffer.toString("utf8");
326
+ const truncationNote = truncated
327
+ ? `<p style="margin:12px 0 0;color:#9ca3af;">Preview truncated to first ${deps.filePreviewMaxBytes.toLocaleString()} bytes.</p>`
328
+ : "";
329
+ deps.sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>File Preview</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre;overflow:auto;max-height:75vh}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">File preview</h1><p style="margin:0 0 12px;color:#9ca3af;">${escapedResolved}</p><p style="margin:0 0 12px;color:#9ca3af;">${deps.escapeHtml(sizeLabel)}</p><pre>${deps.escapeHtml(previewText)}</pre>${truncationNote}<p style="margin:12px 0 0;color:#9ca3af;">Open in terminal with <code>cat '${deps.escapeHtml(shellPath)}'</code></p></body></html>`);
330
+ }
331
+ catch (err) {
332
+ deps.sendHtml(res, 500, `<!doctype html><html><head><meta charset="utf-8"/><title>Preview Error</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Unable to preview path</h1><p style="margin:0 0 12px;color:#9ca3af;">${deps.escapeHtml(deps.safeErrorMessage(err))}</p><pre>${escapedResolved}</pre></body></html>`);
333
+ }
334
+ }, "Open filesystem evidence path");
335
+ router.add("*", "live/filesystem/open", ({ res }) => {
336
+ deps.sendJson(res, 405, { error: "Use GET /orgx/api/live/filesystem/open?path=..." });
337
+ }, "Reject unsupported methods for live/filesystem/open");
338
+ async function renderLiveStream(query, req, res) {
339
+ const write = res.write?.bind(res);
340
+ if (!write) {
341
+ deps.sendJson(res, 501, { error: "Streaming not supported" });
342
+ return;
343
+ }
344
+ const queryString = query.toString();
345
+ const target = `${deps.config.baseUrl.replace(/\/+$/, "")}/api/client/live/stream${queryString ? `?${queryString}` : ""}`;
346
+ let upstreamAbortController = null;
347
+ let reader = null;
348
+ let closed = false;
349
+ let streamOpened = false;
350
+ let idleTimer = null;
351
+ let heartbeatTimer = null;
352
+ let heartbeatBackpressure = false;
353
+ const sseDecoder = new TextDecoder("utf-8");
354
+ let sseBuffer = "";
355
+ const consumeSseText = (chunk) => {
356
+ if (!chunk)
357
+ return;
358
+ sseBuffer += chunk.replace(/\r\n/g, "\n");
359
+ while (true) {
360
+ const boundary = sseBuffer.indexOf("\n\n");
361
+ if (boundary === -1)
362
+ return;
363
+ const rawEvent = sseBuffer.slice(0, boundary);
364
+ sseBuffer = sseBuffer.slice(boundary + 2);
365
+ const lines = rawEvent.split("\n").map((l) => l.replace(/\r$/, ""));
366
+ let eventName = null;
367
+ const dataLines = [];
368
+ for (const line of lines) {
369
+ if (!line)
370
+ continue;
371
+ if (line.startsWith(":"))
372
+ continue;
373
+ if (line.startsWith("event:")) {
374
+ eventName = line.slice("event:".length).trim() || null;
375
+ continue;
376
+ }
377
+ if (line.startsWith("data:")) {
378
+ dataLines.push(line.slice("data:".length).trimStart());
379
+ continue;
380
+ }
381
+ }
382
+ if (!eventName || dataLines.length === 0)
383
+ continue;
384
+ const dataText = dataLines.join("\n").trim();
385
+ if (!dataText)
386
+ continue;
387
+ try {
388
+ if (eventName === "activity.appended") {
389
+ const parsed = JSON.parse(dataText);
390
+ const list = Array.isArray(parsed) ? parsed : [];
391
+ if (list.length > 0) {
392
+ const ctx = toContextBundle(deps.readAgentContexts());
393
+ deps.appendActivityItems(deps.applyAgentContextsToActivity(list, ctx));
394
+ }
395
+ }
396
+ else if (eventName === "snapshot") {
397
+ const parsed = JSON.parse(dataText);
398
+ const list = Array.isArray(parsed?.activity) ? parsed.activity : [];
399
+ if (list.length > 0) {
400
+ const ctx = toContextBundle(deps.readAgentContexts());
401
+ deps.appendActivityItems(deps.applyAgentContextsToActivity(list, ctx));
402
+ }
403
+ }
404
+ }
405
+ catch {
406
+ // ignore malformed payloads
407
+ }
408
+ }
409
+ };
410
+ const clearIdleTimer = () => {
411
+ if (idleTimer) {
412
+ clearTimeout(idleTimer);
413
+ idleTimer = null;
414
+ }
415
+ };
416
+ const clearHeartbeatTimer = () => {
417
+ if (heartbeatTimer) {
418
+ clearInterval(heartbeatTimer);
419
+ heartbeatTimer = null;
420
+ }
421
+ };
422
+ const abortUpstream = () => {
423
+ try {
424
+ upstreamAbortController?.abort();
425
+ }
426
+ catch {
427
+ // best effort
428
+ }
429
+ upstreamAbortController = null;
430
+ };
431
+ const closeStream = () => {
432
+ if (closed)
433
+ return;
434
+ closed = true;
435
+ clearIdleTimer();
436
+ clearHeartbeatTimer();
437
+ abortUpstream();
438
+ if (reader) {
439
+ void reader.cancel().catch(() => undefined);
440
+ }
441
+ if (streamOpened && !res.writableEnded) {
442
+ res.end();
443
+ }
444
+ };
445
+ const resetIdleTimer = () => {
446
+ clearIdleTimer();
447
+ idleTimer = setTimeout(() => {
448
+ closeStream();
449
+ }, deps.streamIdleTimeoutMs);
450
+ };
451
+ try {
452
+ const includeUserHeader = Boolean(deps.config.userId && deps.config.userId.trim().length > 0) &&
453
+ !deps.isUserScopedApiKey(deps.config.apiKey);
454
+ res.writeHead(200, {
455
+ "Content-Type": "text/event-stream; charset=utf-8",
456
+ "Cache-Control": "no-cache, no-transform",
457
+ Connection: "keep-alive",
458
+ ...deps.securityHeaders,
459
+ ...deps.corsHeaders,
460
+ });
461
+ streamOpened = true;
462
+ heartbeatTimer = setInterval(() => {
463
+ if (closed || heartbeatBackpressure)
464
+ return;
465
+ try {
466
+ const accepted = write(Buffer.from(`: ping ${Date.now()}\n`, "utf8"));
467
+ resetIdleTimer();
468
+ if (accepted === false) {
469
+ heartbeatBackpressure = true;
470
+ if (typeof res.once === "function") {
471
+ res.once("drain", () => {
472
+ heartbeatBackpressure = false;
473
+ if (!closed)
474
+ resetIdleTimer();
475
+ });
476
+ }
477
+ }
478
+ }
479
+ catch {
480
+ closeStream();
481
+ }
482
+ }, 20_000);
483
+ heartbeatTimer.unref?.();
484
+ req.on?.("close", closeStream);
485
+ req.on?.("aborted", closeStream);
486
+ res.on?.("close", closeStream);
487
+ res.on?.("finish", closeStream);
488
+ const waitForDrain = async () => {
489
+ if (typeof res.once === "function") {
490
+ await new Promise((resolve) => {
491
+ res.once?.("drain", () => resolve());
492
+ });
493
+ }
494
+ };
495
+ const sleep = async (ms) => {
496
+ await new Promise((resolve) => setTimeout(resolve, ms));
497
+ };
498
+ const connectAndPump = async () => {
499
+ let attempt = 0;
500
+ while (!closed) {
501
+ abortUpstream();
502
+ upstreamAbortController = new AbortController();
503
+ let upstream = null;
504
+ try {
505
+ upstream = await fetch(target, {
506
+ method: "GET",
507
+ headers: {
508
+ Authorization: `Bearer ${deps.config.apiKey}`,
509
+ Accept: "text/event-stream",
510
+ ...(includeUserHeader ? { "X-Orgx-User-Id": deps.config.userId } : {}),
511
+ },
512
+ signal: upstreamAbortController.signal,
513
+ });
514
+ }
515
+ catch {
516
+ upstream = null;
517
+ }
518
+ const contentType = upstream?.headers.get("content-type")?.toLowerCase() ?? "";
519
+ if (!upstream ||
520
+ !upstream.ok ||
521
+ !contentType.includes("text/event-stream") ||
522
+ !upstream.body) {
523
+ const status = upstream?.status ?? null;
524
+ const preview = upstream
525
+ ? (await upstream.text().catch(() => ""))
526
+ .replace(/\s+/g, " ")
527
+ .slice(0, 200)
528
+ : null;
529
+ try {
530
+ write(Buffer.from(`: upstream unavailable status=${status ?? "error"} ${preview ? `preview=${JSON.stringify(preview)}` : ""}\n`, "utf8"));
531
+ }
532
+ catch {
533
+ closeStream();
534
+ return;
535
+ }
536
+ resetIdleTimer();
537
+ attempt += 1;
538
+ const backoffMs = Math.min(15_000, 750 * Math.pow(1.6, Math.min(attempt, 10)));
539
+ await sleep(backoffMs);
540
+ continue;
541
+ }
542
+ attempt = 0;
543
+ reader = upstream.body.getReader();
544
+ const streamReader = reader;
545
+ resetIdleTimer();
546
+ try {
547
+ while (!closed) {
548
+ const { done, value } = await streamReader.read();
549
+ if (done)
550
+ break;
551
+ if (!value || value.byteLength === 0)
552
+ continue;
553
+ resetIdleTimer();
554
+ try {
555
+ consumeSseText(sseDecoder.decode(value, { stream: true }));
556
+ }
557
+ catch {
558
+ // best effort
559
+ }
560
+ const accepted = write(Buffer.from(value));
561
+ if (accepted === false) {
562
+ await waitForDrain();
563
+ }
564
+ }
565
+ }
566
+ catch {
567
+ // swallow; we'll reconnect unless the client is gone
568
+ }
569
+ finally {
570
+ try {
571
+ await streamReader.cancel();
572
+ }
573
+ catch {
574
+ // ignore
575
+ }
576
+ if (reader === streamReader) {
577
+ reader = null;
578
+ }
579
+ }
580
+ if (!closed) {
581
+ await sleep(300);
582
+ }
583
+ }
584
+ };
585
+ void connectAndPump();
586
+ }
587
+ catch (err) {
588
+ closeStream();
589
+ if (!streamOpened && !res.writableEnded) {
590
+ deps.sendJson(res, 500, {
591
+ error: deps.safeErrorMessage(err),
592
+ });
593
+ }
594
+ }
595
+ }
596
+ router.add("GET", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream");
597
+ router.add("HEAD", "live/stream", async ({ query, req, res }) => renderLiveStream(query, req, res), "Proxy live SSE stream (HEAD)");
598
+ }
@@ -0,0 +1,69 @@
1
+ import type { Router } from "../router.js";
2
+ import type { Entity } from "../../types.js";
3
+ type JsonRecord = Record<string, unknown>;
4
+ type LiveInitiativesResponse = {
5
+ initiatives: unknown[];
6
+ total?: number;
7
+ };
8
+ type LiveDecisionsResponse = {
9
+ decisions: Entity[];
10
+ total: number;
11
+ };
12
+ type HandoffsResponse = {
13
+ handoffs: unknown[];
14
+ };
15
+ type LocalSnapshot = Awaited<ReturnType<typeof import("../../local-openclaw.js").loadLocalOpenClawSnapshot>>;
16
+ type RegisterLiveMiscRoutesDeps<TReq, TRes> = {
17
+ parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
18
+ pickString: (input: Record<string, unknown>, keys: string[]) => string | null;
19
+ summarizeActivityHeadline: (input: {
20
+ text: string;
21
+ title: string | null;
22
+ type: string | null;
23
+ }) => Promise<{
24
+ headline: string;
25
+ source: string;
26
+ model: string | null;
27
+ }>;
28
+ getLiveAgents: (input: {
29
+ initiative: string | null;
30
+ includeIdle: boolean | undefined;
31
+ }) => Promise<unknown>;
32
+ getLiveInitiatives: (input: {
33
+ id: string | null;
34
+ limit: number | undefined;
35
+ }) => Promise<LiveInitiativesResponse>;
36
+ getLiveDecisions: (input: {
37
+ status: string;
38
+ limit: number;
39
+ }) => Promise<LiveDecisionsResponse>;
40
+ getHandoffs: () => Promise<HandoffsResponse>;
41
+ loadLocalOpenClawSnapshot: (limit: number) => Promise<LocalSnapshot>;
42
+ toLocalLiveAgents: (snapshot: LocalSnapshot) => {
43
+ agents: Array<{
44
+ initiativeId: string | null;
45
+ status: string;
46
+ }>;
47
+ };
48
+ toLocalLiveInitiatives: (snapshot: LocalSnapshot) => {
49
+ initiatives: Array<{
50
+ id: string;
51
+ title: string;
52
+ status: string;
53
+ updatedAt: string | null;
54
+ sessionCount: number;
55
+ activeAgents: number;
56
+ }>;
57
+ };
58
+ localInitiativeStatusOverrides: Map<string, {
59
+ status: string;
60
+ updatedAt: string;
61
+ }>;
62
+ mapDecisionEntity: (entry: Entity) => {
63
+ waitingMinutes: number;
64
+ };
65
+ sendJson: (res: TRes, status: number, payload: unknown) => void;
66
+ safeErrorMessage: (err: unknown) => string;
67
+ };
68
+ export declare function registerLiveMiscRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterLiveMiscRoutesDeps<TReq, TRes>): void;
69
+ export {};