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