squad-openclaw 2026.2.2203 → 2026.2.2206
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 +7 -0
- package/dist/index.js +128 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,6 +135,13 @@ echo '{"claimToken":"<token>"}' > ~/.openclaw/squad-ceo-data/relay/squad-relay.j
|
|
|
135
135
|
|
|
136
136
|
The claim token is a short-lived, single-use code generated by the relay server for the authenticated user. It links this gateway to the user's Squad account. Once consumed, the relay returns a `roomId` for future reconnections, and the claim token is no longer needed.
|
|
137
137
|
|
|
138
|
+
For local testing, the relay endpoint can be overridden with environment variables:
|
|
139
|
+
|
|
140
|
+
- `OPENCLAW_SQUAD_RELAY_URL` (preferred)
|
|
141
|
+
- `SQUAD_RELAY_URL` (fallback)
|
|
142
|
+
|
|
143
|
+
If neither is set, the plugin uses the production default `wss://relay.squad.ceo`.
|
|
144
|
+
|
|
138
145
|
### How the Relay Connection Works
|
|
139
146
|
|
|
140
147
|
```
|
package/dist/index.js
CHANGED
|
@@ -2638,7 +2638,7 @@ function normalizeEnvelope(raw) {
|
|
|
2638
2638
|
blocking: true
|
|
2639
2639
|
};
|
|
2640
2640
|
}
|
|
2641
|
-
function validateHumanQuestionEnvelope(text) {
|
|
2641
|
+
function validateHumanQuestionEnvelope(text, options) {
|
|
2642
2642
|
const markerIndex = text.indexOf(MARKER);
|
|
2643
2643
|
if (markerIndex < 0) return { valid: false, markerFound: false };
|
|
2644
2644
|
const tail = text.slice(markerIndex + MARKER.length).trimStart();
|
|
@@ -2656,23 +2656,146 @@ function validateHumanQuestionEnvelope(text) {
|
|
|
2656
2656
|
if (!normalized) {
|
|
2657
2657
|
return { valid: false, markerFound: true, errorCode: "schema_invalid" };
|
|
2658
2658
|
}
|
|
2659
|
-
|
|
2659
|
+
const expectedSessionKey = options?.expectedSessionKey?.trim();
|
|
2660
|
+
const sessionKeyMismatch = !!(expectedSessionKey && normalized.sessionKey !== expectedSessionKey);
|
|
2661
|
+
return {
|
|
2662
|
+
valid: true,
|
|
2663
|
+
markerFound: true,
|
|
2664
|
+
normalizedEnvelope: normalized,
|
|
2665
|
+
sessionKeyMismatch
|
|
2666
|
+
};
|
|
2660
2667
|
}
|
|
2661
2668
|
function registerQuestionMethods(api) {
|
|
2662
2669
|
api.registerGatewayMethod(
|
|
2663
2670
|
"squad.questions.validate-envelope",
|
|
2664
2671
|
async ({ params, respond }) => {
|
|
2665
2672
|
const text = typeof params?.text === "string" ? params.text : "";
|
|
2673
|
+
const expectedSessionKey = typeof params?.expectedSessionKey === "string" ? params.expectedSessionKey : void 0;
|
|
2666
2674
|
if (!text) {
|
|
2667
2675
|
respond(false, { error: "Missing 'text' parameter" });
|
|
2668
2676
|
return;
|
|
2669
2677
|
}
|
|
2670
|
-
const result = validateHumanQuestionEnvelope(text);
|
|
2678
|
+
const result = validateHumanQuestionEnvelope(text, { expectedSessionKey });
|
|
2671
2679
|
respond(true, result);
|
|
2672
2680
|
}
|
|
2673
2681
|
);
|
|
2674
2682
|
}
|
|
2675
2683
|
|
|
2684
|
+
// src/sessions.ts
|
|
2685
|
+
function asRecord(value) {
|
|
2686
|
+
return value && typeof value === "object" ? value : null;
|
|
2687
|
+
}
|
|
2688
|
+
function extractSessionKey(value) {
|
|
2689
|
+
const record = asRecord(value);
|
|
2690
|
+
if (!record) return null;
|
|
2691
|
+
if (typeof record.key === "string" && record.key.trim()) {
|
|
2692
|
+
return record.key.trim();
|
|
2693
|
+
}
|
|
2694
|
+
const nestedKey = asRecord(record.key);
|
|
2695
|
+
if (nestedKey && typeof nestedKey.key === "string" && nestedKey.key.trim()) {
|
|
2696
|
+
return nestedKey.key.trim();
|
|
2697
|
+
}
|
|
2698
|
+
if (typeof record.sessionKey === "string" && record.sessionKey.trim()) {
|
|
2699
|
+
return record.sessionKey.trim();
|
|
2700
|
+
}
|
|
2701
|
+
const nestedSession = asRecord(record.session);
|
|
2702
|
+
if (nestedSession && typeof nestedSession.key === "string" && nestedSession.key.trim()) {
|
|
2703
|
+
return nestedSession.key.trim();
|
|
2704
|
+
}
|
|
2705
|
+
return null;
|
|
2706
|
+
}
|
|
2707
|
+
function isInvoker(fn) {
|
|
2708
|
+
return typeof fn === "function";
|
|
2709
|
+
}
|
|
2710
|
+
async function callGatewayAny(ctx, api, method, params) {
|
|
2711
|
+
const ctxGateway = asRecord(ctx.gateway);
|
|
2712
|
+
const apiGateway = asRecord(api?.gateway);
|
|
2713
|
+
const candidates = [
|
|
2714
|
+
ctx.request,
|
|
2715
|
+
ctx.callGatewayMethod,
|
|
2716
|
+
ctx.gatewayRequest,
|
|
2717
|
+
ctx.invokeGatewayMethod,
|
|
2718
|
+
ctxGateway?.request,
|
|
2719
|
+
ctxGateway?.callGatewayMethod,
|
|
2720
|
+
api?.request,
|
|
2721
|
+
api?.callGatewayMethod,
|
|
2722
|
+
api?.gatewayRequest,
|
|
2723
|
+
api?.invokeGatewayMethod,
|
|
2724
|
+
apiGateway?.request,
|
|
2725
|
+
apiGateway?.callGatewayMethod
|
|
2726
|
+
];
|
|
2727
|
+
let lastErr = null;
|
|
2728
|
+
for (const candidate of candidates) {
|
|
2729
|
+
if (!isInvoker(candidate)) continue;
|
|
2730
|
+
try {
|
|
2731
|
+
return await candidate(method, params);
|
|
2732
|
+
} catch (err2) {
|
|
2733
|
+
lastErr = err2;
|
|
2734
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
2735
|
+
if (/unknown method|method .* unavailable|not found|invalid[_ ]request|does not exist/i.test(
|
|
2736
|
+
msg
|
|
2737
|
+
)) {
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
throw err2;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
if (lastErr) throw lastErr;
|
|
2744
|
+
throw new Error("Gateway method invocation API unavailable in plugin context");
|
|
2745
|
+
}
|
|
2746
|
+
function registerSessionMethods(api) {
|
|
2747
|
+
api.registerGatewayMethod(
|
|
2748
|
+
"squad.sessions.create",
|
|
2749
|
+
async (ctx) => {
|
|
2750
|
+
const { params, respond } = ctx;
|
|
2751
|
+
const sessionKey = params?.sessionKey;
|
|
2752
|
+
const agentId = params?.agentId;
|
|
2753
|
+
if (sessionKey !== void 0) {
|
|
2754
|
+
if (typeof sessionKey !== "string" || !sessionKey.trim()) {
|
|
2755
|
+
respond(false, { error: "Invalid 'sessionKey' parameter (must be a non-empty string when provided)" });
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
if (agentId !== void 0) {
|
|
2760
|
+
if (typeof agentId !== "string" || !agentId.trim()) {
|
|
2761
|
+
respond(false, { error: "Invalid 'agentId' parameter (must be a non-empty string when provided)" });
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
const createParams = {
|
|
2766
|
+
...typeof sessionKey === "string" ? { sessionKey: sessionKey.trim() } : {},
|
|
2767
|
+
...typeof agentId === "string" ? { agentId: agentId.trim() } : {}
|
|
2768
|
+
};
|
|
2769
|
+
try {
|
|
2770
|
+
const nativeResult = await callGatewayAny(
|
|
2771
|
+
ctx,
|
|
2772
|
+
api,
|
|
2773
|
+
"sessions.create",
|
|
2774
|
+
createParams
|
|
2775
|
+
);
|
|
2776
|
+
const normalizedKey = extractSessionKey(nativeResult) ?? (typeof sessionKey === "string" ? sessionKey.trim() : null);
|
|
2777
|
+
if (!normalizedKey) {
|
|
2778
|
+
respond(false, {
|
|
2779
|
+
error: "Session creation succeeded but no session key was returned",
|
|
2780
|
+
nativeResult
|
|
2781
|
+
});
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
const nativeRecord = asRecord(nativeResult);
|
|
2785
|
+
respond(true, {
|
|
2786
|
+
...nativeRecord ?? {},
|
|
2787
|
+
key: normalizedKey
|
|
2788
|
+
});
|
|
2789
|
+
} catch (err2) {
|
|
2790
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
2791
|
+
respond(false, {
|
|
2792
|
+
error: `Failed to create session: ${msg}`.slice(0, 500)
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2676
2799
|
// src/shared-api.ts
|
|
2677
2800
|
var CORE_TOOLS = [
|
|
2678
2801
|
"exec",
|
|
@@ -2725,6 +2848,7 @@ function registerSquadSharedApi(api, onFsChange) {
|
|
|
2725
2848
|
registerVersionMethods(api);
|
|
2726
2849
|
registerAgentMethods(api);
|
|
2727
2850
|
registerQuestionMethods(api);
|
|
2851
|
+
registerSessionMethods(api);
|
|
2728
2852
|
const invokeTool = async (tool, args) => {
|
|
2729
2853
|
const executeFn = toolExecutors.get(tool);
|
|
2730
2854
|
if (!executeFn) {
|
|
@@ -2786,7 +2910,7 @@ function squadAppPlugin(api) {
|
|
|
2786
2910
|
const relayState = readRelayState();
|
|
2787
2911
|
const relayEnabled = !!(relayState.claimToken || relayState.roomId);
|
|
2788
2912
|
if (relayEnabled) {
|
|
2789
|
-
const relayUrl = "wss://relay.squad.ceo";
|
|
2913
|
+
const relayUrl = process.env.OPENCLAW_SQUAD_RELAY_URL || process.env.SQUAD_RELAY_URL || "wss://relay.squad.ceo";
|
|
2790
2914
|
startRelayClient(api, relayUrl);
|
|
2791
2915
|
}
|
|
2792
2916
|
}
|
package/package.json
CHANGED