@useorgx/openclaw-plugin 0.3.0 → 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/README.md +48 -1
- 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/adapters/outbox.d.ts +8 -0
- package/dist/adapters/outbox.d.ts.map +1 -0
- package/dist/adapters/outbox.js +6 -0
- package/dist/adapters/outbox.js.map +1 -0
- package/dist/agent-context-store.d.ts +24 -0
- package/dist/agent-context-store.d.ts.map +1 -0
- package/dist/agent-context-store.js +111 -0
- package/dist/agent-context-store.js.map +1 -0
- package/dist/agent-run-store.d.ts +31 -0
- package/dist/agent-run-store.d.ts.map +1 -0
- package/dist/agent-run-store.js +159 -0
- package/dist/agent-run-store.js.map +1 -0
- package/dist/api.d.ts +4 -139
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -347
- package/dist/api.js.map +1 -1
- package/dist/auth-store.d.ts.map +1 -1
- package/dist/auth-store.js +56 -35
- package/dist/auth-store.js.map +1 -1
- package/dist/byok-store.d.ts +11 -0
- package/dist/byok-store.d.ts.map +1 -0
- package/dist/byok-store.js +98 -0
- package/dist/byok-store.js.map +1 -0
- package/dist/contracts/client.d.ts +154 -0
- package/dist/contracts/client.d.ts.map +1 -0
- package/dist/contracts/client.js +422 -0
- package/dist/contracts/client.js.map +1 -0
- package/dist/contracts/types.d.ts +430 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +8 -0
- package/dist/contracts/types.js.map +1 -0
- 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 +27 -1
- package/dist/http-handler.d.ts.map +1 -1
- package/dist/http-handler.js +2520 -92
- package/dist/http-handler.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +352 -27
- package/dist/index.js.map +1 -1
- package/dist/local-openclaw.d.ts.map +1 -1
- package/dist/local-openclaw.js +57 -15
- package/dist/local-openclaw.js.map +1 -1
- package/dist/mcp-apps/orgx-live.html +690 -0
- package/dist/openclaw.plugin.json +3 -3
- package/dist/outbox.d.ts +7 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +150 -17
- 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 +10 -0
- package/dist/snapshot-store.d.ts.map +1 -0
- package/dist/snapshot-store.js +68 -0
- package/dist/snapshot-store.js.map +1 -0
- package/dist/types.d.ts +5 -410
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -4
- package/dist/types.js.map +1 -1
- package/openclaw.plugin.json +3 -3
- package/package.json +13 -3
- package/dashboard/dist/assets/index-BrAP-X_H.css +0 -1
- package/dashboard/dist/assets/index-cOk6qwh-.js +0 -56
- package/dashboard/dist/assets/orgx-logo-QSE5QWy4.png +0 -0
package/dist/index.js
CHANGED
|
@@ -18,9 +18,64 @@ import { homedir } from "node:os";
|
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { clearPersistedApiKey, loadAuthStore, resolveInstallationId, saveAuthStore, } from "./auth-store.js";
|
|
21
|
-
import {
|
|
21
|
+
import { clearPersistedSnapshot, readPersistedSnapshot, writePersistedSnapshot, } from "./snapshot-store.js";
|
|
22
|
+
import { appendToOutbox, readOutbox, readOutboxSummary, replaceOutbox, } from "./outbox.js";
|
|
23
|
+
import { extractProgressOutboxMessage } from "./reporting/outbox-replay.js";
|
|
22
24
|
export { OrgXClient } from "./api.js";
|
|
25
|
+
const DEFAULT_BASE_URL = "https://www.useorgx.com";
|
|
23
26
|
const DEFAULT_DOCS_URL = "https://orgx.mintlify.site/guides/openclaw-plugin-setup";
|
|
27
|
+
function isUserScopedApiKey(apiKey) {
|
|
28
|
+
return apiKey.trim().toLowerCase().startsWith("oxk_");
|
|
29
|
+
}
|
|
30
|
+
function resolveRuntimeUserId(apiKey, candidates) {
|
|
31
|
+
if (isUserScopedApiKey(apiKey)) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
for (const candidate of candidates) {
|
|
35
|
+
if (typeof candidate === "string") {
|
|
36
|
+
const trimmed = candidate.trim();
|
|
37
|
+
if (trimmed.length > 0)
|
|
38
|
+
return trimmed;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
function normalizeHost(value) {
|
|
44
|
+
return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
45
|
+
}
|
|
46
|
+
function isLoopbackHostname(hostname) {
|
|
47
|
+
const normalized = normalizeHost(hostname);
|
|
48
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
49
|
+
}
|
|
50
|
+
function normalizeBaseUrl(raw) {
|
|
51
|
+
const candidate = raw?.trim() ?? "";
|
|
52
|
+
if (!candidate) {
|
|
53
|
+
return DEFAULT_BASE_URL;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const parsed = new URL(candidate);
|
|
57
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
58
|
+
return DEFAULT_BASE_URL;
|
|
59
|
+
}
|
|
60
|
+
// Do not allow credential-bearing URLs.
|
|
61
|
+
if (parsed.username || parsed.password) {
|
|
62
|
+
return DEFAULT_BASE_URL;
|
|
63
|
+
}
|
|
64
|
+
// Plain HTTP is only allowed for local loopback development.
|
|
65
|
+
if (parsed.protocol === "http:" && !isLoopbackHostname(parsed.hostname)) {
|
|
66
|
+
return DEFAULT_BASE_URL;
|
|
67
|
+
}
|
|
68
|
+
parsed.search = "";
|
|
69
|
+
parsed.hash = "";
|
|
70
|
+
const normalizedPath = parsed.pathname.replace(/\/+$/, "");
|
|
71
|
+
parsed.pathname = normalizedPath;
|
|
72
|
+
const normalized = parsed.toString().replace(/\/+$/, "");
|
|
73
|
+
return normalized.length > 0 ? normalized : DEFAULT_BASE_URL;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return DEFAULT_BASE_URL;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
24
79
|
function readLegacyEnvValue(keyPattern) {
|
|
25
80
|
try {
|
|
26
81
|
const envPath = join(homedir(), "Code", "orgx", "orgx", ".env.local");
|
|
@@ -72,7 +127,10 @@ function resolveApiKey(pluginConf, persistedApiKey) {
|
|
|
72
127
|
if (openclaw.apiKey) {
|
|
73
128
|
return { value: openclaw.apiKey, source: "openclaw-config-file" };
|
|
74
129
|
}
|
|
75
|
-
|
|
130
|
+
// For local dev convenience we read `ORGX_API_KEY` from `~/Code/orgx/orgx/.env.local`.
|
|
131
|
+
// Do not auto-consume `ORGX_SERVICE_KEY` because service keys often require `X-Orgx-User-Id`,
|
|
132
|
+
// and the dashboard/client flows are intended to run on user-scoped keys (`oxk_...`).
|
|
133
|
+
const legacy = readLegacyEnvValue(/^ORGX_API_KEY=["']?([^"'\n]+)["']?$/m);
|
|
76
134
|
if (legacy) {
|
|
77
135
|
return { value: legacy, source: "legacy-dev" };
|
|
78
136
|
}
|
|
@@ -92,8 +150,14 @@ function resolvePluginVersion() {
|
|
|
92
150
|
}
|
|
93
151
|
function resolveDocsUrl(baseUrl) {
|
|
94
152
|
const normalized = baseUrl.replace(/\/+$/, "");
|
|
95
|
-
|
|
96
|
-
|
|
153
|
+
try {
|
|
154
|
+
const parsed = new URL(normalized);
|
|
155
|
+
if (isLoopbackHostname(parsed.hostname)) {
|
|
156
|
+
return `${normalized}/docs/mintlify/guides/openclaw-plugin-setup`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return DEFAULT_DOCS_URL;
|
|
97
161
|
}
|
|
98
162
|
return DEFAULT_DOCS_URL;
|
|
99
163
|
}
|
|
@@ -103,15 +167,14 @@ function resolveConfig(api, input) {
|
|
|
103
167
|
const apiKeyResolution = resolveApiKey(pluginConf, input.persistedApiKey);
|
|
104
168
|
const apiKey = apiKeyResolution.value;
|
|
105
169
|
// Resolve user ID for X-Orgx-User-Id header
|
|
106
|
-
const userId =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"https://www.useorgx.com";
|
|
170
|
+
const userId = resolveRuntimeUserId(apiKey, [
|
|
171
|
+
pluginConf.userId,
|
|
172
|
+
process.env.ORGX_USER_ID,
|
|
173
|
+
input.persistedUserId,
|
|
174
|
+
openclaw.userId,
|
|
175
|
+
readLegacyEnvValue(/^ORGX_USER_ID=["']?([^"'\n]+)["']?$/m),
|
|
176
|
+
]);
|
|
177
|
+
const baseUrl = normalizeBaseUrl(pluginConf.baseUrl || process.env.ORGX_BASE_URL || openclaw.baseUrl || DEFAULT_BASE_URL);
|
|
115
178
|
return {
|
|
116
179
|
apiKey,
|
|
117
180
|
userId,
|
|
@@ -168,6 +231,22 @@ function formatSnapshot(snap) {
|
|
|
168
231
|
lines.push(`_Last synced: ${snap.syncedAt}_`);
|
|
169
232
|
return lines.join("\n");
|
|
170
233
|
}
|
|
234
|
+
function apiKeySourceLabel(source) {
|
|
235
|
+
switch (source) {
|
|
236
|
+
case "config":
|
|
237
|
+
return "Plugin Config";
|
|
238
|
+
case "environment":
|
|
239
|
+
return "Environment";
|
|
240
|
+
case "persisted":
|
|
241
|
+
return "Persisted Store";
|
|
242
|
+
case "openclaw-config-file":
|
|
243
|
+
return "OpenClaw Config";
|
|
244
|
+
case "legacy-dev":
|
|
245
|
+
return "Legacy Dev Env";
|
|
246
|
+
default:
|
|
247
|
+
return "Not configured";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
171
250
|
function pickNonEmptyString(...values) {
|
|
172
251
|
for (const value of values) {
|
|
173
252
|
if (typeof value !== "string")
|
|
@@ -206,6 +285,24 @@ function toReportingPhase(phase, progressPct) {
|
|
|
206
285
|
// =============================================================================
|
|
207
286
|
let cachedSnapshot = null;
|
|
208
287
|
let lastSnapshotAt = 0;
|
|
288
|
+
function updateCachedSnapshot(snapshot) {
|
|
289
|
+
cachedSnapshot = snapshot;
|
|
290
|
+
lastSnapshotAt = Date.now();
|
|
291
|
+
try {
|
|
292
|
+
writePersistedSnapshot(snapshot);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// best effort
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function hydrateCachedSnapshot() {
|
|
299
|
+
const persisted = readPersistedSnapshot();
|
|
300
|
+
if (!persisted?.snapshot)
|
|
301
|
+
return;
|
|
302
|
+
cachedSnapshot = persisted.snapshot;
|
|
303
|
+
const ts = Date.parse(persisted.updatedAt);
|
|
304
|
+
lastSnapshotAt = Number.isFinite(ts) ? ts : 0;
|
|
305
|
+
}
|
|
209
306
|
// =============================================================================
|
|
210
307
|
// PLUGIN ENTRY — DEFAULT EXPORT
|
|
211
308
|
// =============================================================================
|
|
@@ -230,6 +327,7 @@ export default function register(api) {
|
|
|
230
327
|
if (!config.apiKey) {
|
|
231
328
|
api.log?.warn?.("[orgx] No API key. Set plugins.entries.orgx.config.apiKey, ORGX_API_KEY env, or ~/Code/orgx/orgx/.env.local");
|
|
232
329
|
}
|
|
330
|
+
hydrateCachedSnapshot();
|
|
233
331
|
const client = new OrgXClient(config.apiKey, config.baseUrl, config.userId);
|
|
234
332
|
let onboardingState = {
|
|
235
333
|
status: config.apiKey ? "connected" : "idle",
|
|
@@ -355,9 +453,7 @@ export default function register(api) {
|
|
|
355
453
|
const nextApiKey = input.apiKey.trim();
|
|
356
454
|
config.apiKey = nextApiKey;
|
|
357
455
|
config.apiKeySource = "persisted";
|
|
358
|
-
|
|
359
|
-
config.userId = input.userId.trim();
|
|
360
|
-
}
|
|
456
|
+
config.userId = resolveRuntimeUserId(nextApiKey, [input.userId, config.userId]);
|
|
361
457
|
client.setCredentials({
|
|
362
458
|
apiKey: config.apiKey,
|
|
363
459
|
userId: config.userId,
|
|
@@ -385,6 +481,154 @@ export default function register(api) {
|
|
|
385
481
|
let syncInFlight = null;
|
|
386
482
|
let syncServiceRunning = false;
|
|
387
483
|
const outboxQueues = ["progress", "decisions", "artifacts"];
|
|
484
|
+
let outboxReplayState = {
|
|
485
|
+
status: "idle",
|
|
486
|
+
lastReplayAttemptAt: null,
|
|
487
|
+
lastReplaySuccessAt: null,
|
|
488
|
+
lastReplayFailureAt: null,
|
|
489
|
+
lastReplayError: null,
|
|
490
|
+
};
|
|
491
|
+
async function buildHealthReport(input = {}) {
|
|
492
|
+
const generatedAt = new Date().toISOString();
|
|
493
|
+
const probeRemote = input.probeRemote === true;
|
|
494
|
+
const outbox = await readOutboxSummary();
|
|
495
|
+
const checks = [];
|
|
496
|
+
const hasApiKey = Boolean(config.apiKey);
|
|
497
|
+
if (hasApiKey) {
|
|
498
|
+
checks.push({
|
|
499
|
+
id: "api_key",
|
|
500
|
+
status: "pass",
|
|
501
|
+
message: `API key detected (${apiKeySourceLabel(config.apiKeySource)}).`,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
checks.push({
|
|
506
|
+
id: "api_key",
|
|
507
|
+
status: "fail",
|
|
508
|
+
message: "API key missing. Connect OrgX in onboarding or set ORGX_API_KEY.",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (syncServiceRunning) {
|
|
512
|
+
checks.push({
|
|
513
|
+
id: "sync_service",
|
|
514
|
+
status: "pass",
|
|
515
|
+
message: "Background sync service is running.",
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
checks.push({
|
|
520
|
+
id: "sync_service",
|
|
521
|
+
status: "warn",
|
|
522
|
+
message: "Background sync service is not running.",
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (outbox.pendingTotal > 0) {
|
|
526
|
+
checks.push({
|
|
527
|
+
id: "outbox",
|
|
528
|
+
status: "warn",
|
|
529
|
+
message: `Outbox has ${outbox.pendingTotal} queued event(s).`,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
checks.push({
|
|
534
|
+
id: "outbox",
|
|
535
|
+
status: "pass",
|
|
536
|
+
message: "Outbox is empty.",
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
let remoteReachable = null;
|
|
540
|
+
let remoteLatencyMs = null;
|
|
541
|
+
let remoteError = null;
|
|
542
|
+
if (probeRemote) {
|
|
543
|
+
if (!hasApiKey) {
|
|
544
|
+
checks.push({
|
|
545
|
+
id: "remote_probe",
|
|
546
|
+
status: "warn",
|
|
547
|
+
message: "Skipped remote probe because API key is missing.",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
const startedAt = Date.now();
|
|
552
|
+
try {
|
|
553
|
+
await client.getOrgSnapshot();
|
|
554
|
+
remoteReachable = true;
|
|
555
|
+
remoteLatencyMs = Date.now() - startedAt;
|
|
556
|
+
checks.push({
|
|
557
|
+
id: "remote_probe",
|
|
558
|
+
status: "pass",
|
|
559
|
+
message: `OrgX API reachable (${remoteLatencyMs}ms).`,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
remoteReachable = false;
|
|
564
|
+
remoteLatencyMs = Date.now() - startedAt;
|
|
565
|
+
remoteError = toErrorMessage(err);
|
|
566
|
+
checks.push({
|
|
567
|
+
id: "remote_probe",
|
|
568
|
+
status: "fail",
|
|
569
|
+
message: `OrgX API probe failed: ${remoteError}`,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (onboardingState.status === "error") {
|
|
575
|
+
checks.push({
|
|
576
|
+
id: "onboarding_state",
|
|
577
|
+
status: "warn",
|
|
578
|
+
message: onboardingState.lastError
|
|
579
|
+
? `Onboarding reports an error: ${onboardingState.lastError}`
|
|
580
|
+
: "Onboarding reports an error state.",
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const hasFail = checks.some((check) => check.status === "fail");
|
|
584
|
+
const hasWarn = checks.some((check) => check.status === "warn");
|
|
585
|
+
const status = hasFail
|
|
586
|
+
? "error"
|
|
587
|
+
: hasWarn
|
|
588
|
+
? "degraded"
|
|
589
|
+
: "ok";
|
|
590
|
+
return {
|
|
591
|
+
ok: status !== "error",
|
|
592
|
+
status,
|
|
593
|
+
generatedAt,
|
|
594
|
+
checks,
|
|
595
|
+
plugin: {
|
|
596
|
+
version: config.pluginVersion,
|
|
597
|
+
installationId: config.installationId,
|
|
598
|
+
enabled: config.enabled,
|
|
599
|
+
dashboardEnabled: config.dashboardEnabled,
|
|
600
|
+
baseUrl: config.baseUrl,
|
|
601
|
+
},
|
|
602
|
+
auth: {
|
|
603
|
+
hasApiKey,
|
|
604
|
+
keySource: config.apiKeySource,
|
|
605
|
+
userIdConfigured: Boolean(config.userId && config.userId.trim().length > 0),
|
|
606
|
+
onboardingStatus: onboardingState.status,
|
|
607
|
+
},
|
|
608
|
+
sync: {
|
|
609
|
+
serviceRunning: syncServiceRunning,
|
|
610
|
+
inFlight: syncInFlight !== null,
|
|
611
|
+
lastSnapshotAt: lastSnapshotAt > 0 ? new Date(lastSnapshotAt).toISOString() : null,
|
|
612
|
+
},
|
|
613
|
+
outbox: {
|
|
614
|
+
pendingTotal: outbox.pendingTotal,
|
|
615
|
+
pendingByQueue: outbox.pendingByQueue,
|
|
616
|
+
oldestEventAt: outbox.oldestEventAt,
|
|
617
|
+
newestEventAt: outbox.newestEventAt,
|
|
618
|
+
replayStatus: outboxReplayState.status,
|
|
619
|
+
lastReplayAttemptAt: outboxReplayState.lastReplayAttemptAt,
|
|
620
|
+
lastReplaySuccessAt: outboxReplayState.lastReplaySuccessAt,
|
|
621
|
+
lastReplayFailureAt: outboxReplayState.lastReplayFailureAt,
|
|
622
|
+
lastReplayError: outboxReplayState.lastReplayError,
|
|
623
|
+
},
|
|
624
|
+
remote: {
|
|
625
|
+
enabled: probeRemote,
|
|
626
|
+
reachable: remoteReachable,
|
|
627
|
+
latencyMs: remoteLatencyMs,
|
|
628
|
+
error: remoteError,
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
}
|
|
388
632
|
function pickStringField(payload, key) {
|
|
389
633
|
const value = payload[key];
|
|
390
634
|
return typeof value === "string" && value.trim().length > 0
|
|
@@ -404,8 +648,8 @@ export default function register(api) {
|
|
|
404
648
|
async function replayOutboxEvent(event) {
|
|
405
649
|
const payload = event.payload ?? {};
|
|
406
650
|
if (event.type === "progress") {
|
|
407
|
-
const
|
|
408
|
-
if (!
|
|
651
|
+
const message = extractProgressOutboxMessage(payload);
|
|
652
|
+
if (!message) {
|
|
409
653
|
api.log?.warn?.("[orgx] Dropping invalid progress outbox event", {
|
|
410
654
|
eventId: event.id,
|
|
411
655
|
});
|
|
@@ -430,7 +674,7 @@ export default function register(api) {
|
|
|
430
674
|
run_id: context.value.runId,
|
|
431
675
|
correlation_id: context.value.correlationId,
|
|
432
676
|
source_client: context.value.sourceClient,
|
|
433
|
-
message
|
|
677
|
+
message,
|
|
434
678
|
phase,
|
|
435
679
|
progress_pct: progressPct,
|
|
436
680
|
level: pickStringField(payload, "level"),
|
|
@@ -516,6 +760,15 @@ export default function register(api) {
|
|
|
516
760
|
}
|
|
517
761
|
}
|
|
518
762
|
async function flushOutboxQueues() {
|
|
763
|
+
const attemptAt = new Date().toISOString();
|
|
764
|
+
outboxReplayState = {
|
|
765
|
+
...outboxReplayState,
|
|
766
|
+
status: "running",
|
|
767
|
+
lastReplayAttemptAt: attemptAt,
|
|
768
|
+
lastReplayError: null,
|
|
769
|
+
};
|
|
770
|
+
let hadReplayFailure = false;
|
|
771
|
+
let lastReplayError = null;
|
|
519
772
|
for (const queue of outboxQueues) {
|
|
520
773
|
const pending = await readOutbox(queue);
|
|
521
774
|
if (pending.length === 0) {
|
|
@@ -527,11 +780,13 @@ export default function register(api) {
|
|
|
527
780
|
await replayOutboxEvent(event);
|
|
528
781
|
}
|
|
529
782
|
catch (err) {
|
|
783
|
+
hadReplayFailure = true;
|
|
784
|
+
lastReplayError = toErrorMessage(err);
|
|
530
785
|
remaining.push(event);
|
|
531
786
|
api.log?.warn?.("[orgx] Outbox replay failed", {
|
|
532
787
|
queue,
|
|
533
788
|
eventId: event.id,
|
|
534
|
-
error:
|
|
789
|
+
error: lastReplayError,
|
|
535
790
|
});
|
|
536
791
|
}
|
|
537
792
|
}
|
|
@@ -545,6 +800,22 @@ export default function register(api) {
|
|
|
545
800
|
});
|
|
546
801
|
}
|
|
547
802
|
}
|
|
803
|
+
if (hadReplayFailure) {
|
|
804
|
+
outboxReplayState = {
|
|
805
|
+
...outboxReplayState,
|
|
806
|
+
status: "error",
|
|
807
|
+
lastReplayFailureAt: new Date().toISOString(),
|
|
808
|
+
lastReplayError,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
outboxReplayState = {
|
|
813
|
+
...outboxReplayState,
|
|
814
|
+
status: "success",
|
|
815
|
+
lastReplaySuccessAt: new Date().toISOString(),
|
|
816
|
+
lastReplayError: null,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
548
819
|
}
|
|
549
820
|
async function doSync() {
|
|
550
821
|
if (syncInFlight) {
|
|
@@ -561,8 +832,7 @@ export default function register(api) {
|
|
|
561
832
|
return;
|
|
562
833
|
}
|
|
563
834
|
try {
|
|
564
|
-
|
|
565
|
-
lastSnapshotAt = Date.now();
|
|
835
|
+
updateCachedSnapshot(await client.getOrgSnapshot());
|
|
566
836
|
updateOnboardingState({
|
|
567
837
|
status: "connected",
|
|
568
838
|
hasApiKey: true,
|
|
@@ -760,17 +1030,16 @@ export default function register(api) {
|
|
|
760
1030
|
lastError: null,
|
|
761
1031
|
nextAction: "enter_manual_key",
|
|
762
1032
|
});
|
|
763
|
-
const probeClient = new OrgXClient(nextKey, config.baseUrl, input.userId
|
|
1033
|
+
const probeClient = new OrgXClient(nextKey, config.baseUrl, resolveRuntimeUserId(nextKey, [input.userId, config.userId]));
|
|
764
1034
|
const snapshot = await probeClient.getOrgSnapshot();
|
|
765
1035
|
setRuntimeApiKey({
|
|
766
1036
|
apiKey: nextKey,
|
|
767
1037
|
source: "manual",
|
|
768
|
-
userId: input.userId
|
|
1038
|
+
userId: resolveRuntimeUserId(nextKey, [input.userId, config.userId]) || null,
|
|
769
1039
|
workspaceName: onboardingState.workspaceName,
|
|
770
1040
|
keyPrefix: null,
|
|
771
1041
|
});
|
|
772
|
-
|
|
773
|
-
lastSnapshotAt = Date.now();
|
|
1042
|
+
updateCachedSnapshot(snapshot);
|
|
774
1043
|
return updateOnboardingState({
|
|
775
1044
|
status: "connected",
|
|
776
1045
|
hasApiKey: true,
|
|
@@ -788,8 +1057,10 @@ export default function register(api) {
|
|
|
788
1057
|
}
|
|
789
1058
|
clearPairingState();
|
|
790
1059
|
clearPersistedApiKey();
|
|
1060
|
+
clearPersistedSnapshot();
|
|
791
1061
|
config.apiKey = "";
|
|
792
|
-
|
|
1062
|
+
config.userId = "";
|
|
1063
|
+
client.setCredentials({ apiKey: "", userId: "" });
|
|
793
1064
|
cachedSnapshot = null;
|
|
794
1065
|
lastSnapshotAt = 0;
|
|
795
1066
|
return updateOnboardingState({
|
|
@@ -1841,6 +2112,58 @@ export default function register(api) {
|
|
|
1841
2112
|
process.exit(1);
|
|
1842
2113
|
}
|
|
1843
2114
|
});
|
|
2115
|
+
cmd
|
|
2116
|
+
.command("doctor")
|
|
2117
|
+
.description("Run plugin diagnostics and connectivity checks")
|
|
2118
|
+
.option("--json", "Print the report as JSON")
|
|
2119
|
+
.option("--no-remote", "Skip remote OrgX API reachability probe")
|
|
2120
|
+
.action(async (opts = {}) => {
|
|
2121
|
+
try {
|
|
2122
|
+
const report = await buildHealthReport({
|
|
2123
|
+
probeRemote: opts.remote !== false,
|
|
2124
|
+
});
|
|
2125
|
+
if (opts.json) {
|
|
2126
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2127
|
+
if (!report.ok)
|
|
2128
|
+
process.exit(1);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
console.log("OrgX Doctor");
|
|
2132
|
+
console.log(` Status: ${report.status.toUpperCase()}`);
|
|
2133
|
+
console.log(` Plugin: v${report.plugin.version}`);
|
|
2134
|
+
console.log(` Base URL: ${report.plugin.baseUrl}`);
|
|
2135
|
+
console.log(` API Key Source: ${apiKeySourceLabel(report.auth.keySource)}`);
|
|
2136
|
+
console.log(` Outbox Pending: ${report.outbox.pendingTotal}`);
|
|
2137
|
+
console.log("");
|
|
2138
|
+
console.log("Checks:");
|
|
2139
|
+
for (const check of report.checks) {
|
|
2140
|
+
const prefix = check.status === "pass"
|
|
2141
|
+
? "[PASS]"
|
|
2142
|
+
: check.status === "warn"
|
|
2143
|
+
? "[WARN]"
|
|
2144
|
+
: "[FAIL]";
|
|
2145
|
+
console.log(` ${prefix} ${check.message}`);
|
|
2146
|
+
}
|
|
2147
|
+
if (report.remote.enabled) {
|
|
2148
|
+
if (report.remote.reachable === true) {
|
|
2149
|
+
console.log(` Remote probe latency: ${report.remote.latencyMs ?? "?"}ms`);
|
|
2150
|
+
}
|
|
2151
|
+
else if (report.remote.reachable === false) {
|
|
2152
|
+
console.log(` Remote probe error: ${report.remote.error ?? "Unknown error"}`);
|
|
2153
|
+
}
|
|
2154
|
+
else {
|
|
2155
|
+
console.log(" Remote probe: skipped");
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (!report.ok) {
|
|
2159
|
+
process.exit(1);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
catch (err) {
|
|
2163
|
+
console.error(`Doctor failed: ${err instanceof Error ? err.message : err}`);
|
|
2164
|
+
process.exit(1);
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
1844
2167
|
}, { commands: ["orgx"] });
|
|
1845
2168
|
// ---------------------------------------------------------------------------
|
|
1846
2169
|
// 4. HTTP Handler — Dashboard + API proxy
|
|
@@ -1851,6 +2174,8 @@ export default function register(api) {
|
|
|
1851
2174
|
getStatus: getPairingStatus,
|
|
1852
2175
|
submitManualKey,
|
|
1853
2176
|
disconnect: disconnectOnboarding,
|
|
2177
|
+
}, {
|
|
2178
|
+
getHealth: async (input = {}) => buildHealthReport({ probeRemote: input.probeRemote === true }),
|
|
1854
2179
|
});
|
|
1855
2180
|
api.registerHttpHandler(httpHandler);
|
|
1856
2181
|
api.log?.info?.("[orgx] Plugin registered", {
|