@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,37 @@
1
+ import type { OrgXClient } from "../../api.js";
2
+ import type { BillingStatus } from "../../types.js";
3
+ export type OpenClawProvider = "anthropic" | "openrouter" | "openai";
4
+ export declare function resolveByokEnvOverrides(): Record<string, string>;
5
+ export declare function modelImpliesByok(model: string | null): boolean;
6
+ export declare function fetchBillingStatusSafe(client: OrgXClient): Promise<BillingStatus | null>;
7
+ export declare function listOpenClawAgents(): Promise<Array<Record<string, unknown>>>;
8
+ export declare function spawnOpenClawAgentTurn(input: {
9
+ agentId: string;
10
+ sessionId: string;
11
+ message: string;
12
+ thinking?: string | null;
13
+ }): {
14
+ pid: number | null;
15
+ };
16
+ export declare function normalizeOpenClawProvider(value: string | null): OpenClawProvider | null;
17
+ export declare function listOpenClawProviderModels(input: {
18
+ agentId: string;
19
+ provider: OpenClawProvider;
20
+ }): Promise<Array<{
21
+ key: string;
22
+ tags: string[];
23
+ }>>;
24
+ export declare function configureOpenClawProviderRouting(input: {
25
+ agentId: string;
26
+ provider: OpenClawProvider;
27
+ requestedModel?: string | null;
28
+ }): Promise<{
29
+ provider: OpenClawProvider;
30
+ model: string;
31
+ }>;
32
+ export declare function resolveAutoOpenClawProvider(): OpenClawProvider | null;
33
+ export declare function isPidAlive(pid: number): boolean;
34
+ export declare function stopDetachedProcess(pid: number): Promise<{
35
+ stopped: boolean;
36
+ wasRunning: boolean;
37
+ }>;
@@ -0,0 +1,283 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readByokKeys } from "../../byok-store.js";
3
+ import { parseJsonSafe } from "../../json-utils.js";
4
+ import { readOpenClawSettingsSnapshot, resolvePreferredOpenClawProvider, } from "../../openclaw-settings.js";
5
+ export function resolveByokEnvOverrides() {
6
+ const stored = readByokKeys();
7
+ const env = {};
8
+ const openai = stored?.openaiApiKey?.trim() ?? "";
9
+ const anthropic = stored?.anthropicApiKey?.trim() ?? "";
10
+ const openrouter = stored?.openrouterApiKey?.trim() ?? "";
11
+ if (openai)
12
+ env.OPENAI_API_KEY = openai;
13
+ if (anthropic)
14
+ env.ANTHROPIC_API_KEY = anthropic;
15
+ if (openrouter)
16
+ env.OPENROUTER_API_KEY = openrouter;
17
+ return env;
18
+ }
19
+ async function runCommandCollect(input) {
20
+ const timeoutMs = input.timeoutMs ?? 10_000;
21
+ return await new Promise((resolve, reject) => {
22
+ const child = spawn(input.command, input.args, {
23
+ env: input.env ? { ...process.env, ...input.env } : process.env,
24
+ stdio: ["ignore", "pipe", "pipe"],
25
+ });
26
+ let stdout = "";
27
+ let stderr = "";
28
+ const timer = timeoutMs
29
+ ? setTimeout(() => {
30
+ try {
31
+ child.kill("SIGKILL");
32
+ }
33
+ catch {
34
+ // best effort
35
+ }
36
+ reject(new Error(`Command timed out after ${timeoutMs}ms`));
37
+ }, timeoutMs)
38
+ : null;
39
+ child.stdout?.on("data", (chunk) => {
40
+ stdout += chunk.toString("utf8");
41
+ });
42
+ child.stderr?.on("data", (chunk) => {
43
+ stderr += chunk.toString("utf8");
44
+ });
45
+ child.on("error", (err) => {
46
+ if (timer)
47
+ clearTimeout(timer);
48
+ reject(err);
49
+ });
50
+ child.on("close", (code) => {
51
+ if (timer)
52
+ clearTimeout(timer);
53
+ resolve({ stdout, stderr, exitCode: typeof code === "number" ? code : null });
54
+ });
55
+ });
56
+ }
57
+ export function modelImpliesByok(model) {
58
+ const lower = (model ?? "").trim().toLowerCase();
59
+ if (!lower)
60
+ return false;
61
+ return (lower.includes("openrouter") ||
62
+ lower.includes("anthropic") ||
63
+ lower.includes("openai"));
64
+ }
65
+ export async function fetchBillingStatusSafe(client) {
66
+ try {
67
+ return await client.getBillingStatus();
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ export async function listOpenClawAgents() {
74
+ const result = await runCommandCollect({
75
+ command: "openclaw",
76
+ args: ["agents", "list", "--json"],
77
+ timeoutMs: 5_000,
78
+ env: resolveByokEnvOverrides(),
79
+ });
80
+ if (result.exitCode !== 0) {
81
+ throw new Error(result.stderr.trim() || "openclaw agents list failed");
82
+ }
83
+ const parsed = parseJsonSafe(result.stdout);
84
+ if (!Array.isArray(parsed)) {
85
+ throw new Error("openclaw agents list returned invalid JSON");
86
+ }
87
+ return parsed.filter((entry) => Boolean(entry && typeof entry === "object"));
88
+ }
89
+ export function spawnOpenClawAgentTurn(input) {
90
+ const args = [
91
+ "agent",
92
+ "--agent",
93
+ input.agentId,
94
+ "--session-id",
95
+ input.sessionId,
96
+ "--message",
97
+ input.message,
98
+ ];
99
+ if (input.thinking) {
100
+ args.push("--thinking", input.thinking);
101
+ }
102
+ const child = spawn("openclaw", args, {
103
+ env: { ...process.env, ...resolveByokEnvOverrides() },
104
+ stdio: "ignore",
105
+ detached: true,
106
+ });
107
+ child.unref();
108
+ return { pid: child.pid ?? null };
109
+ }
110
+ export function normalizeOpenClawProvider(value) {
111
+ const raw = (value ?? "").trim().toLowerCase();
112
+ if (!raw)
113
+ return null;
114
+ if (raw === "auto")
115
+ return null;
116
+ if (raw === "claude")
117
+ return "anthropic";
118
+ if (raw === "anthropic")
119
+ return "anthropic";
120
+ if (raw === "openrouter" || raw === "open-router")
121
+ return "openrouter";
122
+ if (raw === "openai")
123
+ return "openai";
124
+ return null;
125
+ }
126
+ async function setOpenClawAgentModel(input) {
127
+ const agentId = input.agentId.trim();
128
+ const model = input.model.trim();
129
+ if (!agentId || !model) {
130
+ throw new Error("agentId and model are required");
131
+ }
132
+ const result = await runCommandCollect({
133
+ command: "openclaw",
134
+ args: ["models", "--agent", agentId, "set", model],
135
+ timeoutMs: 10_000,
136
+ env: resolveByokEnvOverrides(),
137
+ });
138
+ if (result.exitCode !== 0) {
139
+ throw new Error(result.stderr.trim() || `openclaw models set failed for ${agentId}`);
140
+ }
141
+ }
142
+ export async function listOpenClawProviderModels(input) {
143
+ const providerArgs = input.provider === "openai" ? ["openai-codex", "openai"] : [input.provider];
144
+ let lastError = null;
145
+ for (const providerArg of providerArgs) {
146
+ const result = await runCommandCollect({
147
+ command: "openclaw",
148
+ args: [
149
+ "models",
150
+ "--agent",
151
+ input.agentId,
152
+ "list",
153
+ "--provider",
154
+ providerArg,
155
+ "--json",
156
+ ],
157
+ timeoutMs: 10_000,
158
+ env: resolveByokEnvOverrides(),
159
+ });
160
+ if (result.exitCode !== 0) {
161
+ lastError = new Error(result.stderr.trim() || "openclaw models list failed");
162
+ continue;
163
+ }
164
+ const parsed = parseJsonSafe(result.stdout);
165
+ if (!parsed || typeof parsed !== "object") {
166
+ const trimmed = result.stdout.trim();
167
+ if (!trimmed || /no models found/i.test(trimmed)) {
168
+ if (providerArg === providerArgs[providerArgs.length - 1])
169
+ return [];
170
+ continue;
171
+ }
172
+ lastError = new Error("openclaw models list returned invalid JSON");
173
+ continue;
174
+ }
175
+ const modelsRaw = "models" in parsed && Array.isArray(parsed.models)
176
+ ? parsed.models
177
+ : [];
178
+ const models = modelsRaw
179
+ .map((entry) => {
180
+ if (!entry || typeof entry !== "object")
181
+ return null;
182
+ const row = entry;
183
+ const key = typeof row.key === "string" ? row.key.trim() : "";
184
+ const tags = Array.isArray(row.tags)
185
+ ? row.tags.filter((t) => typeof t === "string")
186
+ : [];
187
+ if (!key)
188
+ return null;
189
+ return { key, tags };
190
+ })
191
+ .filter((entry) => Boolean(entry));
192
+ if (models.length > 0 || providerArg === providerArgs[providerArgs.length - 1]) {
193
+ return models;
194
+ }
195
+ }
196
+ throw lastError ?? new Error("openclaw models list failed");
197
+ }
198
+ function pickPreferredModel(models) {
199
+ if (models.length === 0)
200
+ return null;
201
+ const preferred = models.find((m) => m.tags.some((t) => t === "default"));
202
+ return preferred?.key ?? models[0]?.key ?? null;
203
+ }
204
+ export async function configureOpenClawProviderRouting(input) {
205
+ const requestedModel = (input.requestedModel ?? "").trim() || null;
206
+ // Fast path: use known aliases where possible.
207
+ const aliasByProvider = {
208
+ anthropic: "sonnet",
209
+ openrouter: "sonnet",
210
+ openai: null,
211
+ };
212
+ const candidate = requestedModel ?? aliasByProvider[input.provider];
213
+ if (candidate) {
214
+ try {
215
+ await setOpenClawAgentModel({ agentId: input.agentId, model: candidate });
216
+ return { provider: input.provider, model: candidate };
217
+ }
218
+ catch {
219
+ // Fall through to discovery-based selection.
220
+ }
221
+ }
222
+ const models = await listOpenClawProviderModels({
223
+ agentId: input.agentId,
224
+ provider: input.provider,
225
+ });
226
+ const selected = pickPreferredModel(models);
227
+ if (!selected) {
228
+ throw new Error(`No ${input.provider} models configured for agent ${input.agentId}. Add a model in OpenClaw and retry.`);
229
+ }
230
+ await setOpenClawAgentModel({ agentId: input.agentId, model: selected });
231
+ return { provider: input.provider, model: selected };
232
+ }
233
+ export function resolveAutoOpenClawProvider() {
234
+ try {
235
+ const settings = readOpenClawSettingsSnapshot();
236
+ const provider = resolvePreferredOpenClawProvider(settings.raw);
237
+ if (!provider)
238
+ return null;
239
+ return provider;
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
245
+ export function isPidAlive(pid) {
246
+ if (!Number.isFinite(pid) || pid <= 0)
247
+ return false;
248
+ try {
249
+ process.kill(pid, 0);
250
+ return true;
251
+ }
252
+ catch {
253
+ return false;
254
+ }
255
+ }
256
+ export async function stopDetachedProcess(pid) {
257
+ const alive = isPidAlive(pid);
258
+ if (!alive) {
259
+ return { stopped: true, wasRunning: false };
260
+ }
261
+ const tryKill = (signal) => {
262
+ try {
263
+ // Detached child becomes its own process group (pgid = pid) on Unix.
264
+ process.kill(-pid, signal);
265
+ return;
266
+ }
267
+ catch {
268
+ // Fall back to direct pid kill.
269
+ }
270
+ try {
271
+ process.kill(pid, signal);
272
+ }
273
+ catch {
274
+ // ignore
275
+ }
276
+ };
277
+ tryKill("SIGTERM");
278
+ await new Promise((resolve) => setTimeout(resolve, 450));
279
+ if (isPidAlive(pid)) {
280
+ tryKill("SIGKILL");
281
+ }
282
+ return { stopped: !isPidAlive(pid), wasRunning: true };
283
+ }
@@ -0,0 +1,20 @@
1
+ import { Buffer } from "node:buffer";
2
+ import type { RuntimeInstanceRecord } from "../../runtime-instance-store.js";
3
+ export type RuntimeStreamSubscriber = {
4
+ id: string;
5
+ write: (chunk: Buffer) => boolean;
6
+ end: () => void;
7
+ };
8
+ type CreateRuntimeSseHubDeps = {
9
+ listRuntimeInstances: (input: {
10
+ limit: number;
11
+ }) => RuntimeInstanceRecord[];
12
+ };
13
+ export declare function createRuntimeSseHub(deps: CreateRuntimeSseHubDeps): {
14
+ runtimeStreamSubscribers: Map<string, RuntimeStreamSubscriber>;
15
+ writeRuntimeSseEvent: (subscriber: RuntimeStreamSubscriber, event: string, payload: unknown) => void;
16
+ stopRuntimeStreamTimers: () => void;
17
+ broadcastRuntimeSse: (event: string, payload: unknown) => void;
18
+ ensureRuntimeStreamTimers: () => void;
19
+ };
20
+ export {};
@@ -0,0 +1,110 @@
1
+ import { Buffer } from "node:buffer";
2
+ export function createRuntimeSseHub(deps) {
3
+ const runtimeStreamSubscribers = new Map();
4
+ let runtimeStreamKeepaliveTimer = null;
5
+ let runtimeStreamStalenessTimer = null;
6
+ let runtimeStreamFingerprintById = new Map();
7
+ function runtimeStreamFingerprint(instance) {
8
+ return [
9
+ instance.state,
10
+ instance.lastHeartbeatAt ?? "",
11
+ instance.lastEventAt ?? "",
12
+ instance.progressPct ?? "",
13
+ instance.phase ?? "",
14
+ ].join("|");
15
+ }
16
+ function writeRuntimeSseEvent(subscriber, event, payload) {
17
+ const data = JSON.stringify(payload ?? null);
18
+ subscriber.write(Buffer.from(`event: ${event}
19
+ data: ${data}
20
+
21
+ `, "utf8"));
22
+ }
23
+ function stopRuntimeStreamTimers() {
24
+ if (runtimeStreamKeepaliveTimer) {
25
+ clearInterval(runtimeStreamKeepaliveTimer);
26
+ runtimeStreamKeepaliveTimer = null;
27
+ }
28
+ if (runtimeStreamStalenessTimer) {
29
+ clearInterval(runtimeStreamStalenessTimer);
30
+ runtimeStreamStalenessTimer = null;
31
+ }
32
+ runtimeStreamFingerprintById = new Map();
33
+ }
34
+ function broadcastRuntimeSse(event, payload) {
35
+ if (runtimeStreamSubscribers.size === 0)
36
+ return;
37
+ for (const subscriber of runtimeStreamSubscribers.values()) {
38
+ try {
39
+ writeRuntimeSseEvent(subscriber, event, payload);
40
+ }
41
+ catch {
42
+ try {
43
+ subscriber.end();
44
+ }
45
+ catch {
46
+ // ignore
47
+ }
48
+ runtimeStreamSubscribers.delete(subscriber.id);
49
+ }
50
+ }
51
+ if (runtimeStreamSubscribers.size === 0) {
52
+ stopRuntimeStreamTimers();
53
+ }
54
+ }
55
+ function ensureRuntimeStreamTimers() {
56
+ if (runtimeStreamKeepaliveTimer || runtimeStreamStalenessTimer)
57
+ return;
58
+ runtimeStreamKeepaliveTimer = setInterval(() => {
59
+ if (runtimeStreamSubscribers.size === 0) {
60
+ stopRuntimeStreamTimers();
61
+ return;
62
+ }
63
+ const payload = Buffer.from(`: ping ${Date.now()}
64
+ `, "utf8");
65
+ for (const subscriber of runtimeStreamSubscribers.values()) {
66
+ try {
67
+ subscriber.write(payload);
68
+ }
69
+ catch {
70
+ try {
71
+ subscriber.end();
72
+ }
73
+ catch {
74
+ // ignore
75
+ }
76
+ runtimeStreamSubscribers.delete(subscriber.id);
77
+ }
78
+ }
79
+ }, 20_000);
80
+ runtimeStreamKeepaliveTimer.unref?.();
81
+ runtimeStreamStalenessTimer = setInterval(() => {
82
+ if (runtimeStreamSubscribers.size === 0) {
83
+ stopRuntimeStreamTimers();
84
+ return;
85
+ }
86
+ // listRuntimeInstances applies staleness before returning.
87
+ const instances = deps.listRuntimeInstances({ limit: 600 });
88
+ const nextFingerprintById = new Map();
89
+ for (const instance of instances) {
90
+ const fingerprint = runtimeStreamFingerprint(instance);
91
+ nextFingerprintById.set(instance.id, fingerprint);
92
+ const previous = runtimeStreamFingerprintById.get(instance.id);
93
+ if (previous && previous === fingerprint) {
94
+ continue;
95
+ }
96
+ runtimeStreamFingerprintById.set(instance.id, fingerprint);
97
+ broadcastRuntimeSse("runtime.updated", instance);
98
+ }
99
+ runtimeStreamFingerprintById = nextFingerprintById;
100
+ }, 15_000);
101
+ runtimeStreamStalenessTimer.unref?.();
102
+ }
103
+ return {
104
+ runtimeStreamSubscribers,
105
+ writeRuntimeSseEvent,
106
+ stopRuntimeStreamTimers,
107
+ broadcastRuntimeSse,
108
+ ensureRuntimeStreamTimers,
109
+ };
110
+ }
@@ -0,0 +1,6 @@
1
+ export declare function pickString(record: Record<string, unknown>, keys: string[]): string | null;
2
+ export declare function pickNumber(record: Record<string, unknown>, keys: string[]): number | null;
3
+ export declare function pickHeaderString(headers: Record<string, string | string[] | undefined>, keys: string[]): string | null;
4
+ export declare function toIsoString(value: string | null): string | null;
5
+ export declare function parsePositiveInt(raw: string | null, fallback: number): number;
6
+ export declare function parseBooleanQuery(raw: string | null): boolean;
@@ -0,0 +1,67 @@
1
+ export function pickString(record, keys) {
2
+ for (const key of keys) {
3
+ const value = record[key];
4
+ if (typeof value === "string") {
5
+ const trimmed = value.trim();
6
+ if (trimmed.length > 0)
7
+ return trimmed;
8
+ }
9
+ }
10
+ return null;
11
+ }
12
+ export function pickNumber(record, keys) {
13
+ for (const key of keys) {
14
+ const value = record[key];
15
+ if (typeof value === "number" && Number.isFinite(value)) {
16
+ return value;
17
+ }
18
+ if (typeof value === "string" && value.trim().length > 0) {
19
+ const parsed = Number(value);
20
+ if (Number.isFinite(parsed))
21
+ return parsed;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ export function pickHeaderString(headers, keys) {
27
+ for (const key of keys) {
28
+ const candidates = [key, key.toLowerCase(), key.toUpperCase()];
29
+ for (const candidate of candidates) {
30
+ const raw = headers[candidate];
31
+ if (typeof raw === "string" && raw.trim().length > 0) {
32
+ return raw.trim();
33
+ }
34
+ if (Array.isArray(raw)) {
35
+ const first = raw.find((value) => typeof value === "string" && value.trim().length > 0);
36
+ if (first)
37
+ return first.trim();
38
+ }
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ export function toIsoString(value) {
44
+ if (!value)
45
+ return null;
46
+ const parsed = Date.parse(value);
47
+ if (!Number.isFinite(parsed))
48
+ return null;
49
+ return new Date(parsed).toISOString();
50
+ }
51
+ export function parsePositiveInt(raw, fallback) {
52
+ if (!raw)
53
+ return fallback;
54
+ const parsed = Number(raw);
55
+ if (!Number.isFinite(parsed))
56
+ return fallback;
57
+ return Math.max(1, Math.floor(parsed));
58
+ }
59
+ export function parseBooleanQuery(raw) {
60
+ if (!raw)
61
+ return false;
62
+ const normalized = raw.trim().toLowerCase();
63
+ return (normalized === "1" ||
64
+ normalized === "true" ||
65
+ normalized === "yes" ||
66
+ normalized === "on");
67
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * HTTP Handler — Serves the React dashboard SPA and API proxy endpoints.
3
+ *
4
+ * Registered at the `/orgx` prefix. Handles:
5
+ * /orgx/live → dashboard SPA (index.html)
6
+ * /orgx/live/assets/* → static assets (JS, CSS, images)
7
+ * /orgx/api/status → org status summary
8
+ * /orgx/api/agents → agent states
9
+ * /orgx/api/activity → activity feed
10
+ * /orgx/api/initiatives → initiative data
11
+ * /orgx/api/health → plugin diagnostics + outbox/sync status
12
+ * /orgx/api/onboarding → onboarding / config state
13
+ * /orgx/api/agent-suite/status → suite provisioning plan (OpenClaw-local)
14
+ * /orgx/api/agent-suite/install → install/update suite (OpenClaw-local)
15
+ * /orgx/api/delegation/preflight → delegation preflight
16
+ * /orgx/api/runs/:id/checkpoints → list/create checkpoints
17
+ * /orgx/api/runs/:id/checkpoints/:checkpointId/restore → restore checkpoint
18
+ * /orgx/api/runs/:id/actions/:action → run control action
19
+ */
20
+ import type { OrgXClient } from "../api.js";
21
+ import type { OnboardingState, OrgXConfig, OrgSnapshot } from "../types.js";
22
+ import { type OutboxAdapter } from "../adapters/outbox.js";
23
+ type OpenClawAdapter = {
24
+ listAgents?: () => Promise<Array<Record<string, unknown>>>;
25
+ spawnAgentTurn?: (input: {
26
+ agentId: string;
27
+ sessionId: string;
28
+ message: string;
29
+ thinking?: string | null;
30
+ }) => {
31
+ pid: number | null;
32
+ };
33
+ stopDetachedProcess?: (pid: number) => Promise<{
34
+ stopped: boolean;
35
+ wasRunning: boolean;
36
+ }>;
37
+ isPidAlive?: (pid: number) => boolean;
38
+ };
39
+ interface PluginRequest {
40
+ method?: string;
41
+ url?: string;
42
+ headers: Record<string, string | string[] | undefined>;
43
+ body?: unknown;
44
+ on?: (event: string, listener: (...args: unknown[]) => void) => void;
45
+ once?: (event: string, listener: (...args: unknown[]) => void) => void;
46
+ }
47
+ interface PluginResponse {
48
+ writeHead(status: number, headers?: Record<string, string>): void;
49
+ end(body?: string | Buffer): void;
50
+ write?(chunk: string | Buffer): boolean | void;
51
+ on?: (event: string, listener: (...args: unknown[]) => void) => void;
52
+ once?: (event: string, listener: (...args: unknown[]) => void) => void;
53
+ writableEnded?: boolean;
54
+ }
55
+ interface OnboardingController {
56
+ getState: () => OnboardingState;
57
+ startPairing: (input: {
58
+ openclawVersion?: string;
59
+ platform?: string;
60
+ deviceName?: string;
61
+ }) => Promise<{
62
+ pairingId: string;
63
+ connectUrl: string;
64
+ expiresAt: string;
65
+ pollIntervalMs: number;
66
+ state: OnboardingState;
67
+ }>;
68
+ getStatus: () => Promise<OnboardingState>;
69
+ submitManualKey: (input: {
70
+ apiKey: string;
71
+ userId?: string;
72
+ }) => Promise<OnboardingState>;
73
+ disconnect: () => Promise<OnboardingState>;
74
+ }
75
+ interface DiagnosticsProvider {
76
+ getHealth?: (input?: {
77
+ probeRemote?: boolean;
78
+ }) => Promise<unknown>;
79
+ }
80
+ export declare function createHttpHandler(config: OrgXConfig & {
81
+ dashboardEnabled?: boolean;
82
+ pluginVersion?: string;
83
+ installationId?: string | null;
84
+ }, client: OrgXClient, getSnapshot: () => OrgSnapshot | null, onboarding: OnboardingController, diagnostics?: DiagnosticsProvider, adapters?: {
85
+ outbox?: OutboxAdapter;
86
+ openclaw?: OpenClawAdapter;
87
+ }): (req: PluginRequest, res: PluginResponse) => Promise<boolean>;
88
+ export {};