@useorgx/openclaw-plugin 0.4.6 → 0.4.8
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 +310 -24
- package/dashboard/dist/assets/BNeJ0kpF.js +1 -0
- package/dashboard/dist/assets/BzkiMPmM.js +215 -0
- package/dashboard/dist/assets/CUV9IHHi.js +1 -0
- package/dashboard/dist/assets/Ie7d9Iq2.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/activity-actor-fields.d.ts +3 -0
- package/dist/activity-actor-fields.js +128 -0
- package/dist/activity-store.js +8 -1
- package/dist/artifacts/register-artifact.d.ts +47 -0
- package/dist/artifacts/register-artifact.js +271 -0
- package/dist/auth-store.js +8 -13
- package/dist/contracts/client.d.ts +1 -0
- package/dist/contracts/client.js +7 -5
- package/dist/contracts/types.d.ts +4 -0
- package/dist/http-handler.js +1055 -114
- package/dist/index.js +165 -29
- package/dist/local-openclaw.js +8 -0
- package/dist/mcp-client-setup.js +75 -90
- package/dist/runtime-instance-store.js +3 -3
- package/dist/worker-supervisor.js +15 -0
- package/package.json +6 -1
- package/dashboard/dist/assets/0tOC3wSN.js +0 -214
- package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
- package/dashboard/dist/assets/CyxZio4Y.js +0 -1
- package/dashboard/dist/assets/DaAIOik3.css +0 -1
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { OrgXClient } from "./api.js";
|
|
14
14
|
import { createHttpHandler } from "./http-handler.js";
|
|
15
15
|
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan } from "./agent-suite.js";
|
|
16
|
-
import {
|
|
16
|
+
import { registerArtifact } from "./artifacts/register-artifact.js";
|
|
17
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { homedir } from "node:os";
|
|
@@ -39,6 +39,16 @@ function isUserScopedApiKey(apiKey) {
|
|
|
39
39
|
}
|
|
40
40
|
function resolveRuntimeUserId(apiKey, candidates) {
|
|
41
41
|
if (isUserScopedApiKey(apiKey)) {
|
|
42
|
+
// For oxk_ keys, the OrgX API ignores X-Orgx-User-Id, but we still keep a UUID
|
|
43
|
+
// around for created_by_id on certain entity writes (e.g., work_artifacts).
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
if (typeof candidate !== "string")
|
|
46
|
+
continue;
|
|
47
|
+
const trimmed = candidate.trim();
|
|
48
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
42
52
|
return "";
|
|
43
53
|
}
|
|
44
54
|
for (const candidate of candidates) {
|
|
@@ -1211,6 +1221,10 @@ export default function register(api) {
|
|
|
1211
1221
|
description: null,
|
|
1212
1222
|
agentId: stopped.agentId,
|
|
1213
1223
|
agentName: null,
|
|
1224
|
+
requesterAgentId: stopped.agentId ?? null,
|
|
1225
|
+
requesterAgentName: null,
|
|
1226
|
+
executorAgentId: stopped.agentId ?? null,
|
|
1227
|
+
executorAgentName: null,
|
|
1214
1228
|
runId: stopped.runId,
|
|
1215
1229
|
initiativeId,
|
|
1216
1230
|
timestamp,
|
|
@@ -1241,6 +1255,10 @@ export default function register(api) {
|
|
|
1241
1255
|
description: null,
|
|
1242
1256
|
agentId: stopped.agentId,
|
|
1243
1257
|
agentName: null,
|
|
1258
|
+
requesterAgentId: stopped.agentId ?? null,
|
|
1259
|
+
requesterAgentName: null,
|
|
1260
|
+
executorAgentId: stopped.agentId ?? null,
|
|
1261
|
+
executorAgentName: null,
|
|
1244
1262
|
runId: stopped.runId,
|
|
1245
1263
|
initiativeId,
|
|
1246
1264
|
timestamp,
|
|
@@ -1651,16 +1669,55 @@ export default function register(api) {
|
|
|
1651
1669
|
return;
|
|
1652
1670
|
}
|
|
1653
1671
|
if (event.type === "artifact") {
|
|
1654
|
-
// Artifacts are
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1672
|
+
// Artifacts are first-class UX loop closure (activity stream + entity modals).
|
|
1673
|
+
// Try to persist upstream; if this fails, keep the event queued for retry.
|
|
1674
|
+
const payload = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
|
|
1675
|
+
? event.payload
|
|
1676
|
+
: {};
|
|
1677
|
+
const name = pickStringField(payload, "name") ?? pickStringField(payload, "title") ?? "";
|
|
1678
|
+
const artifactType = pickStringField(payload, "artifact_type") ?? "other";
|
|
1679
|
+
const entityType = pickStringField(payload, "entity_type") ?? "";
|
|
1680
|
+
const entityId = pickStringField(payload, "entity_id") ?? "";
|
|
1681
|
+
const artifactId = pickStringField(payload, "artifact_id") ?? null;
|
|
1682
|
+
const description = pickStringField(payload, "description") ?? undefined;
|
|
1683
|
+
const externalUrl = pickStringField(payload, "url") ?? pickStringField(payload, "artifact_url") ?? null;
|
|
1684
|
+
const content = pickStringField(payload, "content") ?? pickStringField(payload, "preview_markdown") ?? null;
|
|
1685
|
+
const allowedEntityType = entityType === "initiative" ||
|
|
1686
|
+
entityType === "milestone" ||
|
|
1687
|
+
entityType === "task" ||
|
|
1688
|
+
entityType === "decision" ||
|
|
1689
|
+
entityType === "project"
|
|
1690
|
+
? entityType
|
|
1691
|
+
: null;
|
|
1692
|
+
if (!allowedEntityType || !entityId.trim() || !name.trim()) {
|
|
1693
|
+
api.log?.warn?.("[orgx] Dropping invalid artifact outbox event", {
|
|
1694
|
+
eventId: event.id,
|
|
1695
|
+
entityType,
|
|
1696
|
+
entityId,
|
|
1697
|
+
});
|
|
1698
|
+
return;
|
|
1661
1699
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1700
|
+
const result = await registerArtifact(client, client.getBaseUrl(), {
|
|
1701
|
+
artifact_id: artifactId,
|
|
1702
|
+
entity_type: allowedEntityType,
|
|
1703
|
+
entity_id: entityId,
|
|
1704
|
+
name: name.trim(),
|
|
1705
|
+
artifact_type: artifactType.trim() || "other",
|
|
1706
|
+
description,
|
|
1707
|
+
external_url: externalUrl,
|
|
1708
|
+
preview_markdown: content,
|
|
1709
|
+
status: "draft",
|
|
1710
|
+
metadata: {
|
|
1711
|
+
source: "outbox_replay",
|
|
1712
|
+
outbox_event_id: event.id,
|
|
1713
|
+
...(payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata)
|
|
1714
|
+
? payload.metadata
|
|
1715
|
+
: {}),
|
|
1716
|
+
},
|
|
1717
|
+
validate_persistence: process.env.ORGX_VALIDATE_ARTIFACT_PERSISTENCE === "1",
|
|
1718
|
+
});
|
|
1719
|
+
if (!result.ok) {
|
|
1720
|
+
throw new Error(result.persistence.last_error ?? "artifact registration failed");
|
|
1664
1721
|
}
|
|
1665
1722
|
return;
|
|
1666
1723
|
}
|
|
@@ -1996,9 +2053,15 @@ export default function register(api) {
|
|
|
1996
2053
|
nextAction: "retry",
|
|
1997
2054
|
});
|
|
1998
2055
|
}
|
|
2056
|
+
const pairingUserIdRaw = typeof polled.data.supabaseUserId === "string"
|
|
2057
|
+
? polled.data.supabaseUserId
|
|
2058
|
+
: typeof polled.data.userId === "string"
|
|
2059
|
+
? polled.data.userId
|
|
2060
|
+
: null;
|
|
1999
2061
|
setRuntimeApiKey({
|
|
2000
2062
|
apiKey: key,
|
|
2001
2063
|
source: "browser_pairing",
|
|
2064
|
+
userId: resolveRuntimeUserId(key, [pairingUserIdRaw, config.userId]) || null,
|
|
2002
2065
|
workspaceName: polled.data.workspaceName ?? null,
|
|
2003
2066
|
keyPrefix: polled.data.keyPrefix ?? null,
|
|
2004
2067
|
});
|
|
@@ -2749,6 +2812,10 @@ export default function register(api) {
|
|
|
2749
2812
|
description: payload.next_step ?? null,
|
|
2750
2813
|
agentId: null,
|
|
2751
2814
|
agentName: null,
|
|
2815
|
+
requesterAgentId: null,
|
|
2816
|
+
requesterAgentName: null,
|
|
2817
|
+
executorAgentId: null,
|
|
2818
|
+
executorAgentName: null,
|
|
2752
2819
|
runId: context.value.runId ?? null,
|
|
2753
2820
|
initiativeId: context.value.initiativeId,
|
|
2754
2821
|
timestamp: now,
|
|
@@ -2798,6 +2865,10 @@ export default function register(api) {
|
|
|
2798
2865
|
description: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
2799
2866
|
agentId: null,
|
|
2800
2867
|
agentName: null,
|
|
2868
|
+
requesterAgentId: null,
|
|
2869
|
+
requesterAgentName: null,
|
|
2870
|
+
executorAgentId: null,
|
|
2871
|
+
executorAgentName: null,
|
|
2801
2872
|
runId: context.value.runId ?? null,
|
|
2802
2873
|
initiativeId: context.value.initiativeId,
|
|
2803
2874
|
timestamp: now,
|
|
@@ -3077,13 +3148,22 @@ export default function register(api) {
|
|
|
3077
3148
|
// --- orgx_register_artifact ---
|
|
3078
3149
|
registerMcpTool({
|
|
3079
3150
|
name: "orgx_register_artifact",
|
|
3080
|
-
description: "Register a work output (PR, document, config change, report, etc.)
|
|
3151
|
+
description: "Register a work output (PR, document, config change, report, etc.) as a work_artifact in OrgX. Makes it visible in the dashboard activity timeline and entity detail modals.",
|
|
3081
3152
|
parameters: {
|
|
3082
3153
|
type: "object",
|
|
3083
3154
|
properties: {
|
|
3084
3155
|
initiative_id: {
|
|
3085
3156
|
type: "string",
|
|
3086
|
-
description: "
|
|
3157
|
+
description: "Convenience: initiative UUID. Used as entity_type='initiative', entity_id=<this> when entity_type/entity_id are not provided.",
|
|
3158
|
+
},
|
|
3159
|
+
entity_type: {
|
|
3160
|
+
type: "string",
|
|
3161
|
+
enum: ["initiative", "milestone", "task", "decision", "project"],
|
|
3162
|
+
description: "The type of entity this artifact is attached to",
|
|
3163
|
+
},
|
|
3164
|
+
entity_id: {
|
|
3165
|
+
type: "string",
|
|
3166
|
+
description: "UUID of the entity this artifact is attached to",
|
|
3087
3167
|
},
|
|
3088
3168
|
name: {
|
|
3089
3169
|
type: "string",
|
|
@@ -3091,8 +3171,7 @@ export default function register(api) {
|
|
|
3091
3171
|
},
|
|
3092
3172
|
artifact_type: {
|
|
3093
3173
|
type: "string",
|
|
3094
|
-
|
|
3095
|
-
description: "Type of artifact",
|
|
3174
|
+
description: "Artifact type code (e.g., 'eng.diff_pack', 'pr', 'document'). Falls back to 'shared.project_handbook' if the type is not recognized by OrgX.",
|
|
3096
3175
|
},
|
|
3097
3176
|
description: {
|
|
3098
3177
|
type: "string",
|
|
@@ -3100,7 +3179,11 @@ export default function register(api) {
|
|
|
3100
3179
|
},
|
|
3101
3180
|
url: {
|
|
3102
3181
|
type: "string",
|
|
3103
|
-
description: "
|
|
3182
|
+
description: "External link to the artifact (PR URL, file path, etc.)",
|
|
3183
|
+
},
|
|
3184
|
+
content: {
|
|
3185
|
+
type: "string",
|
|
3186
|
+
description: "Inline preview content (markdown/text). At least one of url or content is required.",
|
|
3104
3187
|
},
|
|
3105
3188
|
},
|
|
3106
3189
|
required: ["name", "artifact_type"],
|
|
@@ -3109,9 +3192,32 @@ export default function register(api) {
|
|
|
3109
3192
|
async execute(_callId, params = { name: "", artifact_type: "other" }) {
|
|
3110
3193
|
const now = new Date().toISOString();
|
|
3111
3194
|
const id = `artifact:${randomUUID().slice(0, 8)}`;
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3195
|
+
// Resolve entity association: explicit entity_type+entity_id > initiative_id > inferred
|
|
3196
|
+
let resolvedEntityType = null;
|
|
3197
|
+
let resolvedEntityId = null;
|
|
3198
|
+
if (params.entity_type && isUuid(params.entity_id)) {
|
|
3199
|
+
resolvedEntityType = params.entity_type;
|
|
3200
|
+
resolvedEntityId = params.entity_id;
|
|
3201
|
+
}
|
|
3202
|
+
else if (isUuid(params.initiative_id)) {
|
|
3203
|
+
resolvedEntityType = "initiative";
|
|
3204
|
+
resolvedEntityId = params.initiative_id;
|
|
3205
|
+
}
|
|
3206
|
+
else {
|
|
3207
|
+
const inferred = inferReportingInitiativeId(params);
|
|
3208
|
+
if (inferred) {
|
|
3209
|
+
resolvedEntityType = "initiative";
|
|
3210
|
+
resolvedEntityId = inferred;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
if (!resolvedEntityType || !resolvedEntityId) {
|
|
3214
|
+
return text("❌ Cannot register artifact: provide entity_type + entity_id, or initiative_id, so the artifact can be attached to an entity.");
|
|
3215
|
+
}
|
|
3216
|
+
if (!params.url && !params.content) {
|
|
3217
|
+
return text("❌ Cannot register artifact: provide at least one of url or content.");
|
|
3218
|
+
}
|
|
3219
|
+
const baseUrl = client.getBaseUrl();
|
|
3220
|
+
const artifactId = randomUUID();
|
|
3115
3221
|
const activityItem = {
|
|
3116
3222
|
id,
|
|
3117
3223
|
type: "artifact_created",
|
|
@@ -3119,35 +3225,65 @@ export default function register(api) {
|
|
|
3119
3225
|
description: params.description ?? null,
|
|
3120
3226
|
agentId: null,
|
|
3121
3227
|
agentName: null,
|
|
3228
|
+
requesterAgentId: null,
|
|
3229
|
+
requesterAgentName: null,
|
|
3230
|
+
executorAgentId: null,
|
|
3231
|
+
executorAgentName: null,
|
|
3122
3232
|
runId: null,
|
|
3123
|
-
initiativeId,
|
|
3233
|
+
initiativeId: resolvedEntityType === "initiative" ? resolvedEntityId : null,
|
|
3124
3234
|
timestamp: now,
|
|
3125
3235
|
summary: params.url ?? null,
|
|
3126
3236
|
metadata: withProvenanceMetadata({
|
|
3127
3237
|
source: "orgx_register_artifact",
|
|
3128
3238
|
artifact_type: params.artifact_type,
|
|
3129
3239
|
url: params.url,
|
|
3240
|
+
entity_type: resolvedEntityType,
|
|
3241
|
+
entity_id: resolvedEntityId,
|
|
3130
3242
|
}),
|
|
3131
3243
|
};
|
|
3132
3244
|
try {
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3245
|
+
const result = await registerArtifact(client, baseUrl, {
|
|
3246
|
+
artifact_id: artifactId,
|
|
3247
|
+
entity_type: resolvedEntityType,
|
|
3248
|
+
entity_id: resolvedEntityId,
|
|
3249
|
+
name: params.name,
|
|
3135
3250
|
artifact_type: params.artifact_type,
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
status: "
|
|
3251
|
+
description: params.description ?? null,
|
|
3252
|
+
external_url: params.url ?? null,
|
|
3253
|
+
preview_markdown: params.content ?? null,
|
|
3254
|
+
status: "draft",
|
|
3255
|
+
metadata: {
|
|
3256
|
+
source: "orgx_register_artifact",
|
|
3257
|
+
artifact_id: artifactId,
|
|
3258
|
+
},
|
|
3259
|
+
validate_persistence: true,
|
|
3260
|
+
});
|
|
3261
|
+
if (!result.ok) {
|
|
3262
|
+
throw new Error(result.persistence.last_error ?? "Artifact registration failed");
|
|
3263
|
+
}
|
|
3264
|
+
activityItem.metadata = withProvenanceMetadata({
|
|
3265
|
+
...activityItem.metadata,
|
|
3266
|
+
artifact_id: result.artifact_id,
|
|
3267
|
+
entity_type: resolvedEntityType,
|
|
3268
|
+
entity_id: resolvedEntityId,
|
|
3140
3269
|
});
|
|
3141
|
-
return json(`Artifact registered: ${params.name} [${params.artifact_type}]`,
|
|
3270
|
+
return json(`Artifact registered: ${params.name} [${params.artifact_type}] → ${resolvedEntityType}/${resolvedEntityId} (id: ${result.artifact_id})`, result);
|
|
3142
3271
|
}
|
|
3143
|
-
catch {
|
|
3272
|
+
catch (firstError) {
|
|
3273
|
+
// Outbox fallback for offline/error scenarios
|
|
3144
3274
|
await appendToOutbox("artifacts", {
|
|
3145
3275
|
id,
|
|
3146
3276
|
type: "artifact",
|
|
3147
3277
|
timestamp: now,
|
|
3148
3278
|
payload: {
|
|
3149
|
-
|
|
3150
|
-
|
|
3279
|
+
artifact_id: artifactId,
|
|
3280
|
+
name: params.name,
|
|
3281
|
+
artifact_type: params.artifact_type,
|
|
3282
|
+
description: params.description,
|
|
3283
|
+
url: params.url,
|
|
3284
|
+
content: params.content,
|
|
3285
|
+
entity_type: resolvedEntityType,
|
|
3286
|
+
entity_id: resolvedEntityId,
|
|
3151
3287
|
},
|
|
3152
3288
|
activityItem,
|
|
3153
3289
|
});
|
package/dist/local-openclaw.js
CHANGED
|
@@ -580,6 +580,10 @@ function turnToActivity(turn, session, cachedSummary, index) {
|
|
|
580
580
|
description: modelAlias,
|
|
581
581
|
agentId: session.agentId,
|
|
582
582
|
agentName: session.agentName,
|
|
583
|
+
requesterAgentId: session.agentId ?? null,
|
|
584
|
+
requesterAgentName: session.agentName ?? null,
|
|
585
|
+
executorAgentId: session.agentId ?? null,
|
|
586
|
+
executorAgentName: session.agentName ?? null,
|
|
583
587
|
runId: session.sessionId ?? session.key,
|
|
584
588
|
initiativeId: session.agentId ? `agent:${session.agentId}` : null,
|
|
585
589
|
timestamp: turn.timestamp,
|
|
@@ -747,6 +751,10 @@ function makeSessionSummaryItem(session) {
|
|
|
747
751
|
description: modelAlias ? `Local session (${modelAlias})` : "Local session",
|
|
748
752
|
agentId: session.agentId,
|
|
749
753
|
agentName: session.agentName,
|
|
754
|
+
requesterAgentId: session.agentId ?? null,
|
|
755
|
+
requesterAgentName: session.agentName ?? null,
|
|
756
|
+
executorAgentId: session.agentId ?? null,
|
|
757
|
+
executorAgentName: session.agentName ?? null,
|
|
750
758
|
runId: session.sessionId ?? session.key,
|
|
751
759
|
initiativeId: session.agentId ? `agent:${session.agentId}` : null,
|
|
752
760
|
timestamp: session.updatedAt ?? new Date().toISOString(),
|
package/dist/mcp-client-setup.js
CHANGED
|
@@ -5,22 +5,6 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { writeFileAtomicSync, writeJsonFileAtomicSync } from "./fs-utils.js";
|
|
6
6
|
const ORGX_LOCAL_MCP_KEY = "orgx-openclaw";
|
|
7
7
|
const ORGX_HOSTED_MCP_URL = "https://mcp.useorgx.com/mcp";
|
|
8
|
-
const ORGX_LOCAL_MCP_SCOPES = [
|
|
9
|
-
"engineering",
|
|
10
|
-
"product",
|
|
11
|
-
"design",
|
|
12
|
-
"marketing",
|
|
13
|
-
"sales",
|
|
14
|
-
"operations",
|
|
15
|
-
"orchestration",
|
|
16
|
-
];
|
|
17
|
-
function scopedMcpServerKey(scope) {
|
|
18
|
-
return `${ORGX_LOCAL_MCP_KEY}-${scope}`;
|
|
19
|
-
}
|
|
20
|
-
function scopedMcpUrl(localMcpUrl, scope) {
|
|
21
|
-
const base = localMcpUrl.replace(/\/+$/, "");
|
|
22
|
-
return `${base}/${scope}`;
|
|
23
|
-
}
|
|
24
8
|
function escapeRegExp(value) {
|
|
25
9
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26
10
|
}
|
|
@@ -59,6 +43,20 @@ function backupFileSync(path, mode) {
|
|
|
59
43
|
return null;
|
|
60
44
|
}
|
|
61
45
|
}
|
|
46
|
+
function removeLegacyScopedMcpServers(servers) {
|
|
47
|
+
const next = { ...servers };
|
|
48
|
+
let updated = false;
|
|
49
|
+
const scopedPrefix = `${ORGX_LOCAL_MCP_KEY}-`;
|
|
50
|
+
for (const key of Object.keys(next)) {
|
|
51
|
+
if (key === ORGX_LOCAL_MCP_KEY)
|
|
52
|
+
continue;
|
|
53
|
+
if (!key.startsWith(scopedPrefix))
|
|
54
|
+
continue;
|
|
55
|
+
delete next[key];
|
|
56
|
+
updated = true;
|
|
57
|
+
}
|
|
58
|
+
return { updated, next };
|
|
59
|
+
}
|
|
62
60
|
export function patchClaudeMcpConfig(input) {
|
|
63
61
|
const currentServers = isRecord(input.current.mcpServers) ? input.current.mcpServers : {};
|
|
64
62
|
const existingOrgx = isRecord(currentServers.orgx) ? currentServers.orgx : {};
|
|
@@ -88,41 +86,20 @@ export function patchClaudeMcpConfig(input) {
|
|
|
88
86
|
? existing.description
|
|
89
87
|
: "OrgX platform via local OpenClaw plugin (no OAuth)",
|
|
90
88
|
};
|
|
91
|
-
const
|
|
92
|
-
const scopedEntries = {};
|
|
93
|
-
for (const scope of ORGX_LOCAL_MCP_SCOPES) {
|
|
94
|
-
const key = scopedMcpServerKey(scope);
|
|
95
|
-
const expectedUrl = scopedMcpUrl(input.localMcpUrl, scope);
|
|
96
|
-
const existingScoped = isRecord(currentServers[key]) ? currentServers[key] : {};
|
|
97
|
-
const priorUrl = typeof existingScoped.url === "string" ? existingScoped.url : "";
|
|
98
|
-
const priorType = typeof existingScoped.type === "string" ? existingScoped.type : "";
|
|
99
|
-
const nextScoped = {
|
|
100
|
-
...existingScoped,
|
|
101
|
-
type: "http",
|
|
102
|
-
url: expectedUrl,
|
|
103
|
-
description: typeof existingScoped.description === "string" && existingScoped.description.trim().length > 0
|
|
104
|
-
? existingScoped.description
|
|
105
|
-
: `OrgX platform via local OpenClaw plugin (${scope} scope)`,
|
|
106
|
-
};
|
|
107
|
-
scopedEntries[key] = nextScoped;
|
|
108
|
-
if (priorUrl !== expectedUrl || priorType !== "http") {
|
|
109
|
-
updatedScopes.push(scope);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
const nextServers = {
|
|
89
|
+
const mergedServers = {
|
|
113
90
|
...currentServers,
|
|
114
91
|
...(shouldSetHostedOrgx ? { orgx: nextOrgxEntry } : {}),
|
|
115
92
|
[ORGX_LOCAL_MCP_KEY]: nextEntry,
|
|
116
|
-
...scopedEntries,
|
|
117
93
|
};
|
|
94
|
+
const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
|
|
118
95
|
const next = {
|
|
119
96
|
...input.current,
|
|
120
|
-
mcpServers:
|
|
97
|
+
mcpServers: scopedCleanup.next,
|
|
121
98
|
};
|
|
122
99
|
const updatedLocal = priorUrl !== input.localMcpUrl || priorType !== "http";
|
|
123
100
|
const updatedHosted = shouldSetHostedOrgx &&
|
|
124
101
|
(existingOrgxUrl !== ORGX_HOSTED_MCP_URL || existingOrgxType !== "http");
|
|
125
|
-
const updated = updatedLocal || updatedHosted ||
|
|
102
|
+
const updated = updatedLocal || updatedHosted || scopedCleanup.updated;
|
|
126
103
|
return { updated, next };
|
|
127
104
|
}
|
|
128
105
|
export function patchCursorMcpConfig(input) {
|
|
@@ -133,30 +110,16 @@ export function patchCursorMcpConfig(input) {
|
|
|
133
110
|
...existing,
|
|
134
111
|
url: input.localMcpUrl,
|
|
135
112
|
};
|
|
136
|
-
const
|
|
137
|
-
let updatedScopes = false;
|
|
138
|
-
for (const scope of ORGX_LOCAL_MCP_SCOPES) {
|
|
139
|
-
const key = scopedMcpServerKey(scope);
|
|
140
|
-
const expectedUrl = scopedMcpUrl(input.localMcpUrl, scope);
|
|
141
|
-
const existingScoped = isRecord(currentServers[key]) ? currentServers[key] : {};
|
|
142
|
-
const priorScopedUrl = typeof existingScoped.url === "string" ? existingScoped.url : "";
|
|
143
|
-
scopedEntries[key] = {
|
|
144
|
-
...existingScoped,
|
|
145
|
-
url: expectedUrl,
|
|
146
|
-
};
|
|
147
|
-
if (priorScopedUrl !== expectedUrl)
|
|
148
|
-
updatedScopes = true;
|
|
149
|
-
}
|
|
150
|
-
const nextServers = {
|
|
113
|
+
const mergedServers = {
|
|
151
114
|
...currentServers,
|
|
152
115
|
[ORGX_LOCAL_MCP_KEY]: nextEntry,
|
|
153
|
-
...scopedEntries,
|
|
154
116
|
};
|
|
117
|
+
const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
|
|
155
118
|
const next = {
|
|
156
119
|
...input.current,
|
|
157
|
-
mcpServers:
|
|
120
|
+
mcpServers: scopedCleanup.next,
|
|
158
121
|
};
|
|
159
|
-
const updated = priorUrl !== input.localMcpUrl ||
|
|
122
|
+
const updated = priorUrl !== input.localMcpUrl || scopedCleanup.updated;
|
|
160
123
|
return { updated, next };
|
|
161
124
|
}
|
|
162
125
|
function upsertCodexMcpServerSection(input) {
|
|
@@ -173,7 +136,9 @@ function upsertCodexMcpServerSection(input) {
|
|
|
173
136
|
}
|
|
174
137
|
const urlLine = `url = "${input.url}"`;
|
|
175
138
|
if (headerIndex === -1) {
|
|
176
|
-
const
|
|
139
|
+
const needsQuote = /[^A-Za-z0-9_]/.test(input.key);
|
|
140
|
+
const keyLiteral = needsQuote ? `"${input.key}"` : input.key;
|
|
141
|
+
const suffix = ["", `[mcp_servers.${keyLiteral}]`, urlLine, ""].join("\n");
|
|
177
142
|
const normalized = currentText.endsWith("\n") ? currentText : `${currentText}\n`;
|
|
178
143
|
return { updated: true, next: `${normalized}${suffix}` };
|
|
179
144
|
}
|
|
@@ -221,29 +186,52 @@ function upsertCodexMcpServerSection(input) {
|
|
|
221
186
|
}
|
|
222
187
|
return { updated, next: `${lines.join("\n")}\n` };
|
|
223
188
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
current = `${normalized}${suffix}`;
|
|
243
|
-
updatedHosted = true;
|
|
189
|
+
function removeCodexLegacyScopedMcpSections(input) {
|
|
190
|
+
const lines = input.current.split(/\r?\n/);
|
|
191
|
+
const scopedPrefix = `${input.baseKey}-`;
|
|
192
|
+
let updated = false;
|
|
193
|
+
let index = 0;
|
|
194
|
+
while (index < lines.length) {
|
|
195
|
+
const trimmed = lines[index].trim();
|
|
196
|
+
const headerMatch = trimmed.match(/^\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_]+))\]\s*$/);
|
|
197
|
+
if (!headerMatch) {
|
|
198
|
+
index += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const key = (headerMatch[1] ?? headerMatch[2] ?? "").trim();
|
|
202
|
+
const isLegacyScoped = key !== input.baseKey &&
|
|
203
|
+
key.startsWith(scopedPrefix);
|
|
204
|
+
if (!isLegacyScoped) {
|
|
205
|
+
index += 1;
|
|
206
|
+
continue;
|
|
244
207
|
}
|
|
208
|
+
let sectionEnd = lines.length;
|
|
209
|
+
for (let i = index + 1; i < lines.length; i += 1) {
|
|
210
|
+
if (lines[i].trim().startsWith("[")) {
|
|
211
|
+
sectionEnd = i;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const start = index > 0 && lines[index - 1].trim() === "" ? index - 1 : index;
|
|
216
|
+
lines.splice(start, sectionEnd - start);
|
|
217
|
+
updated = true;
|
|
218
|
+
index = Math.max(0, start);
|
|
245
219
|
}
|
|
220
|
+
return { updated, next: `${lines.join("\n")}\n` };
|
|
221
|
+
}
|
|
222
|
+
export function patchCodexConfigToml(input) {
|
|
223
|
+
let current = input.current;
|
|
246
224
|
let updated = false;
|
|
225
|
+
// Ensure the hosted OrgX entry uses a direct `url` (streamable HTTP) so that
|
|
226
|
+
// `codex mcp login orgx` can perform OAuth. Route through upsertCodexMcpServerSection
|
|
227
|
+
// so stale stdio-transport fields (command/args) are stripped automatically.
|
|
228
|
+
const hosted = upsertCodexMcpServerSection({
|
|
229
|
+
current,
|
|
230
|
+
key: "orgx",
|
|
231
|
+
url: ORGX_HOSTED_MCP_URL,
|
|
232
|
+
});
|
|
233
|
+
updated = updated || hosted.updated;
|
|
234
|
+
current = hosted.next;
|
|
247
235
|
const base = upsertCodexMcpServerSection({
|
|
248
236
|
current,
|
|
249
237
|
key: ORGX_LOCAL_MCP_KEY,
|
|
@@ -251,16 +239,13 @@ export function patchCodexConfigToml(input) {
|
|
|
251
239
|
});
|
|
252
240
|
updated = updated || base.updated;
|
|
253
241
|
current = base.next;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
current = next.next;
|
|
262
|
-
}
|
|
263
|
-
return { updated: updatedHosted || updated, next: current };
|
|
242
|
+
const removedScoped = removeCodexLegacyScopedMcpSections({
|
|
243
|
+
current,
|
|
244
|
+
baseKey: ORGX_LOCAL_MCP_KEY,
|
|
245
|
+
});
|
|
246
|
+
updated = updated || removedScoped.updated;
|
|
247
|
+
current = removedScoped.next;
|
|
248
|
+
return { updated, next: current };
|
|
264
249
|
}
|
|
265
250
|
export async function autoConfigureDetectedMcpClients(input) {
|
|
266
251
|
const logger = input.logger ?? {};
|
|
@@ -85,7 +85,7 @@ function normalizeHookEvent(value) {
|
|
|
85
85
|
}
|
|
86
86
|
function toProviderLogo(sourceClient) {
|
|
87
87
|
if (sourceClient === "codex")
|
|
88
|
-
return "
|
|
88
|
+
return "openai";
|
|
89
89
|
if (sourceClient === "claude-code")
|
|
90
90
|
return "anthropic";
|
|
91
91
|
if (sourceClient === "openclaw")
|
|
@@ -97,9 +97,9 @@ function toProviderLogo(sourceClient) {
|
|
|
97
97
|
function normalizeProviderLogo(value, sourceClient) {
|
|
98
98
|
const normalized = normalizeNullableString(value)?.toLowerCase();
|
|
99
99
|
if (normalized === "codex")
|
|
100
|
-
return "codex";
|
|
100
|
+
return sourceClient === "codex" ? "openai" : "codex";
|
|
101
101
|
if (normalized === "openai")
|
|
102
|
-
return
|
|
102
|
+
return "openai";
|
|
103
103
|
if (normalized === "anthropic")
|
|
104
104
|
return "anthropic";
|
|
105
105
|
if (normalized === "openclaw")
|
|
@@ -4,6 +4,17 @@ function pickString(value) {
|
|
|
4
4
|
const trimmed = value.trim();
|
|
5
5
|
return trimmed.length > 0 ? trimmed : null;
|
|
6
6
|
}
|
|
7
|
+
function parseIgnoredMcpServers(raw) {
|
|
8
|
+
if (typeof raw !== "string")
|
|
9
|
+
return new Set(["codex_apps"]);
|
|
10
|
+
const trimmed = raw.trim();
|
|
11
|
+
if (!trimmed || trimmed.toLowerCase() === "none")
|
|
12
|
+
return new Set();
|
|
13
|
+
return new Set(trimmed
|
|
14
|
+
.split(",")
|
|
15
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
16
|
+
.filter(Boolean));
|
|
17
|
+
}
|
|
7
18
|
export function detectMcpHandshakeFailure(logText) {
|
|
8
19
|
const text = String(logText ?? "");
|
|
9
20
|
const lower = text.toLowerCase();
|
|
@@ -28,6 +39,10 @@ export function detectMcpHandshakeFailure(logText) {
|
|
|
28
39
|
signalLine?.match(/mcp client for\s+\[?([^\]]+)\]?\s+failed to start/i) ??
|
|
29
40
|
null;
|
|
30
41
|
const server = serverMatch ? pickString(serverMatch[1]) ?? null : null;
|
|
42
|
+
const ignoredServers = parseIgnoredMcpServers(process.env.ORGX_AUTOPILOT_MCP_HANDSHAKE_IGNORE_SERVERS);
|
|
43
|
+
if (server && ignoredServers.has(server.toLowerCase())) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
31
46
|
return {
|
|
32
47
|
kind: "mcp_handshake",
|
|
33
48
|
server,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useorgx/openclaw-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "OrgX plugin for OpenClaw — agent orchestration, quality gates, model routing, and live dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"verify:clean-install": "node ./scripts/verify-clean-install.mjs",
|
|
41
41
|
"verify:agent-suite": "npm run build:core && node ./scripts/verify-agent-suite-install.mjs",
|
|
42
42
|
"verify:billing": "npm run build:core && node ./scripts/verify-billing-scenarios.mjs",
|
|
43
|
+
"verify:autopilot-e2e:local": "npm run build:core && node ./scripts/verify-autopilot-e2e-local.mjs",
|
|
44
|
+
"verify:autopilot-e2e:matrix": "npm run build:core && node ./scripts/verify-autopilot-e2e-matrix.mjs",
|
|
45
|
+
"verify:autopilot-e2e:entities": "npm run build:core && node ./scripts/verify-autopilot-e2e-entities.mjs",
|
|
46
|
+
"verify:autopilot-e2e:entities-matrix": "npm run build:core && node ./scripts/verify-autopilot-e2e-entities-matrix.mjs",
|
|
47
|
+
"verify:iwmt-cascade": "npm run build:core && node ./scripts/verify-iwmt-cascade-e2e.mjs",
|
|
43
48
|
"e2e:auto-continue": "node ./scripts/e2e-auto-continue.mjs",
|
|
44
49
|
"e2e:agent-suite": "npm run build:core && node ./scripts/e2e-agent-suite-kickoff-3x.mjs",
|
|
45
50
|
"demo:record": "node ./scripts/record-demo.mjs",
|