squad-openclaw 2026.2.2201 → 2026.2.2205

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 (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +207 -35
  3. 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
@@ -2183,7 +2183,7 @@ function registerSqlTools(api) {
2183
2183
  }
2184
2184
 
2185
2185
  // src/version.ts
2186
- import { execSync as execSync2 } from "child_process";
2186
+ import { spawn } from "child_process";
2187
2187
  import fs9 from "fs";
2188
2188
  import path10 from "path";
2189
2189
  import { fileURLToPath } from "url";
@@ -2193,6 +2193,9 @@ var updateInProgress = false;
2193
2193
  var VERIFY_TIMEOUT_MS = 2e4;
2194
2194
  var VERIFY_INTERVAL_MS = 500;
2195
2195
  var RESTART_BUFFER_MS = 5e3;
2196
+ var REGISTRY_TIMEOUT_MS = 2500;
2197
+ var COMMAND_OUTPUT_LIMIT = 8e3;
2198
+ var COMMAND_KILL_GRACE_MS = 2e3;
2196
2199
  function readInstalledVersionFromConfig() {
2197
2200
  try {
2198
2201
  const raw = fs9.readFileSync(CONFIG_PATH, "utf-8");
@@ -2243,9 +2246,9 @@ function getCurrentVersion() {
2243
2246
  return "0.0.0";
2244
2247
  }
2245
2248
  }
2246
- async function fetchLatestVersion() {
2249
+ async function fetchLatestVersion(timeoutMs = REGISTRY_TIMEOUT_MS) {
2247
2250
  const controller = new AbortController();
2248
- const timeout = setTimeout(() => controller.abort(), 1e4);
2251
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
2249
2252
  try {
2250
2253
  const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, {
2251
2254
  signal: controller.signal
@@ -2257,14 +2260,64 @@ async function fetchLatestVersion() {
2257
2260
  clearTimeout(timeout);
2258
2261
  }
2259
2262
  }
2260
- function runDoctorFixSilently() {
2261
- try {
2262
- execSync2("openclaw doctor --fix 2>/dev/null || true", {
2263
- timeout: 3e4,
2264
- encoding: "utf-8"
2263
+ async function runCommand(args, timeoutMs) {
2264
+ return new Promise((resolve) => {
2265
+ const child = spawn("openclaw", args, {
2266
+ stdio: ["ignore", "pipe", "pipe"],
2267
+ detached: true
2265
2268
  });
2266
- } catch {
2267
- }
2269
+ let output = "";
2270
+ let timedOut = false;
2271
+ let resolved = false;
2272
+ let killGraceTimer = null;
2273
+ const append = (chunk) => {
2274
+ output += chunk.toString();
2275
+ if (output.length > COMMAND_OUTPUT_LIMIT) {
2276
+ output = output.slice(output.length - COMMAND_OUTPUT_LIMIT);
2277
+ }
2278
+ };
2279
+ const finalize = (result) => {
2280
+ if (resolved) return;
2281
+ resolved = true;
2282
+ if (killGraceTimer) clearTimeout(killGraceTimer);
2283
+ clearTimeout(timeout);
2284
+ resolve(result);
2285
+ };
2286
+ child.stdout?.on("data", append);
2287
+ child.stderr?.on("data", append);
2288
+ child.on("error", (err2) => {
2289
+ append(String(err2));
2290
+ finalize({
2291
+ ok: false,
2292
+ timedOut,
2293
+ exitCode: null,
2294
+ signal: null,
2295
+ output
2296
+ });
2297
+ });
2298
+ child.on("close", (code, signal) => {
2299
+ finalize({
2300
+ ok: !timedOut && code === 0,
2301
+ timedOut,
2302
+ exitCode: code,
2303
+ signal,
2304
+ output
2305
+ });
2306
+ });
2307
+ const timeout = setTimeout(() => {
2308
+ timedOut = true;
2309
+ try {
2310
+ process.kill(-child.pid, "SIGTERM");
2311
+ } catch {
2312
+ }
2313
+ killGraceTimer = setTimeout(() => {
2314
+ try {
2315
+ process.kill(-child.pid, "SIGKILL");
2316
+ } catch {
2317
+ }
2318
+ }, COMMAND_KILL_GRACE_MS);
2319
+ }, timeoutMs);
2320
+ });
2268
2321
  }
2269
2322
  function sleep(ms) {
2270
2323
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -2399,13 +2452,25 @@ async function waitForVerifiedInstall() {
2399
2452
  function registerVersionMethods(api) {
2400
2453
  api.registerGatewayMethod(
2401
2454
  "squad.version.check",
2402
- async ({ respond }) => {
2455
+ async ({ respond, params }) => {
2403
2456
  try {
2457
+ const checkParams = params ?? {};
2404
2458
  const current = getCurrentVersion();
2459
+ if (checkParams.skipRegistry) {
2460
+ console.log("[version_check_local] returning local version only");
2461
+ respond(true, {
2462
+ current,
2463
+ latest: null,
2464
+ updateAvailable: false,
2465
+ registrySkipped: true
2466
+ });
2467
+ return;
2468
+ }
2405
2469
  let latest;
2406
2470
  try {
2407
2471
  latest = await fetchLatestVersion();
2408
2472
  } catch {
2473
+ console.log("[version_check_registry_timeout] npm registry unavailable");
2409
2474
  respond(true, {
2410
2475
  current,
2411
2476
  latest: null,
@@ -2433,6 +2498,7 @@ function registerVersionMethods(api) {
2433
2498
  return;
2434
2499
  }
2435
2500
  updateInProgress = true;
2501
+ const updateAttemptId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
2436
2502
  try {
2437
2503
  const before = getCurrentVersion();
2438
2504
  const beforeInstalledVersion = readInstalledVersionFromConfig();
@@ -2448,19 +2514,25 @@ function registerVersionMethods(api) {
2448
2514
  configBackup = fs9.readFileSync(CONFIG_PATH, "utf-8");
2449
2515
  } catch {
2450
2516
  }
2451
- runDoctorFixSilently();
2517
+ await runCommand(["doctor", "--fix"], 3e4);
2452
2518
  try {
2453
- updateOutput = execSync2(
2454
- `openclaw plugins update ${PACKAGE_NAME} 2>&1`,
2455
- { timeout: 12e4, encoding: "utf-8" }
2456
- );
2519
+ const first = await runCommand(["plugins", "update", PACKAGE_NAME], 12e4);
2520
+ updateOutput = first.output;
2521
+ if (!first.ok) {
2522
+ throw new Error(
2523
+ first.timedOut ? "UPDATE_TIMEOUT: plugins update timed out" : `UPDATE_FAILED: plugins update exited with code ${String(first.exitCode)}`
2524
+ );
2525
+ }
2457
2526
  } catch (firstErr) {
2458
- runDoctorFixSilently();
2527
+ await runCommand(["doctor", "--fix"], 3e4);
2459
2528
  try {
2460
- updateOutput = execSync2(
2461
- `openclaw plugins update ${PACKAGE_NAME} 2>&1`,
2462
- { timeout: 12e4, encoding: "utf-8" }
2463
- );
2529
+ const retry = await runCommand(["plugins", "update", PACKAGE_NAME], 12e4);
2530
+ updateOutput = retry.output;
2531
+ if (!retry.ok) {
2532
+ throw new Error(
2533
+ retry.timedOut ? "UPDATE_TIMEOUT: plugins update timed out after retry" : `UPDATE_FAILED: plugins update retry exited with code ${String(retry.exitCode)}`
2534
+ );
2535
+ }
2464
2536
  } catch (installErr) {
2465
2537
  if (configBackup) {
2466
2538
  try {
@@ -2471,9 +2543,11 @@ function registerVersionMethods(api) {
2471
2543
  const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
2472
2544
  const retryMsg = installErr instanceof Error ? installErr.message : String(installErr);
2473
2545
  respond(false, {
2546
+ code: /UPDATE_TIMEOUT/.test(retryMsg) ? "UPDATE_TIMEOUT" : "UPDATE_FAILED",
2474
2547
  error: `Update failed after doctor fix retry: ${retryMsg}`,
2475
2548
  output: updateOutput,
2476
- firstError: firstMsg
2549
+ firstError: firstMsg,
2550
+ updateAttemptId
2477
2551
  });
2478
2552
  return;
2479
2553
  }
@@ -2487,9 +2561,11 @@ function registerVersionMethods(api) {
2487
2561
  }
2488
2562
  }
2489
2563
  respond(false, {
2564
+ code: "VERIFY_FAILED",
2490
2565
  error: `Update verification failed: ${verification.reason ?? "unknown error"}`,
2491
2566
  output: updateOutput.slice(0, 500),
2492
- verification
2567
+ verification,
2568
+ updateAttemptId
2493
2569
  });
2494
2570
  return;
2495
2571
  }
@@ -2498,10 +2574,12 @@ function registerVersionMethods(api) {
2498
2574
  if (beforeInstalledVersion && verificationAfterReconcile.packageVersion && beforeInstalledVersion === verificationAfterReconcile.packageVersion) {
2499
2575
  const alreadyLatest = !!latestVersion && compareVersions(verificationAfterReconcile.packageVersion, latestVersion) >= 0;
2500
2576
  respond(false, {
2577
+ code: "UPDATE_FAILED",
2501
2578
  error: alreadyLatest ? `Already at latest version (${verificationAfterReconcile.packageVersion}).` : `Update command completed but installed version did not change (${verificationAfterReconcile.packageVersion}).`,
2502
2579
  output: updateOutput.slice(0, 500),
2503
2580
  verification: verificationAfterReconcile,
2504
- latestVersion
2581
+ latestVersion,
2582
+ updateAttemptId
2505
2583
  });
2506
2584
  return;
2507
2585
  }
@@ -2514,22 +2592,27 @@ function registerVersionMethods(api) {
2514
2592
  restartInMs: RESTART_BUFFER_MS,
2515
2593
  verification: verificationAfterReconcile,
2516
2594
  latestVersion,
2517
- output: updateOutput.slice(0, 500)
2595
+ output: updateOutput.slice(0, 500),
2596
+ updateAttemptId
2518
2597
  });
2519
2598
  await sleep(RESTART_BUFFER_MS);
2520
2599
  console.log(
2521
2600
  `[version] Plugin update verified (was ${before}), restarting gateway...`
2522
2601
  );
2523
- try {
2524
- execSync2("openclaw gateway restart 2>&1", {
2525
- timeout: 3e4,
2526
- encoding: "utf-8"
2602
+ const restartResult = await runCommand(["gateway", "restart"], 3e4);
2603
+ if (!restartResult.ok && !restartResult.timedOut) {
2604
+ console.log("[update_cmd_timeout] restart command failed unexpectedly", {
2605
+ updateAttemptId,
2606
+ exitCode: restartResult.exitCode,
2607
+ signal: restartResult.signal
2527
2608
  });
2528
- } catch {
2529
2609
  }
2530
2610
  } catch (e) {
2531
2611
  const msg = e instanceof Error ? e.message : String(e);
2532
- respond(false, { error: msg });
2612
+ respond(false, {
2613
+ code: /VERIFY_FAILED/.test(msg) ? "VERIFY_FAILED" : /UPDATE_TIMEOUT/.test(msg) ? "UPDATE_TIMEOUT" : "UPDATE_FAILED",
2614
+ error: msg
2615
+ });
2533
2616
  } finally {
2534
2617
  updateInProgress = false;
2535
2618
  }
@@ -2555,7 +2638,7 @@ function normalizeEnvelope(raw) {
2555
2638
  blocking: true
2556
2639
  };
2557
2640
  }
2558
- function validateHumanQuestionEnvelope(text) {
2641
+ function validateHumanQuestionEnvelope(text, options) {
2559
2642
  const markerIndex = text.indexOf(MARKER);
2560
2643
  if (markerIndex < 0) return { valid: false, markerFound: false };
2561
2644
  const tail = text.slice(markerIndex + MARKER.length).trimStart();
@@ -2573,23 +2656,111 @@ function validateHumanQuestionEnvelope(text) {
2573
2656
  if (!normalized) {
2574
2657
  return { valid: false, markerFound: true, errorCode: "schema_invalid" };
2575
2658
  }
2576
- return { valid: true, markerFound: true, normalizedEnvelope: normalized };
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
+ };
2577
2667
  }
2578
2668
  function registerQuestionMethods(api) {
2579
2669
  api.registerGatewayMethod(
2580
2670
  "squad.questions.validate-envelope",
2581
2671
  async ({ params, respond }) => {
2582
2672
  const text = typeof params?.text === "string" ? params.text : "";
2673
+ const expectedSessionKey = typeof params?.expectedSessionKey === "string" ? params.expectedSessionKey : void 0;
2583
2674
  if (!text) {
2584
2675
  respond(false, { error: "Missing 'text' parameter" });
2585
2676
  return;
2586
2677
  }
2587
- const result = validateHumanQuestionEnvelope(text);
2678
+ const result = validateHumanQuestionEnvelope(text, { expectedSessionKey });
2588
2679
  respond(true, result);
2589
2680
  }
2590
2681
  );
2591
2682
  }
2592
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 registerSessionMethods(api) {
2708
+ const callGateway = async (ctx, method, params = {}) => {
2709
+ const ctxRequest = ctx.request;
2710
+ if (typeof ctxRequest === "function") return ctxRequest(method, params);
2711
+ const apiRequest = api?.request;
2712
+ if (typeof apiRequest === "function") return apiRequest(method, params);
2713
+ const apiCallGatewayMethod = api?.callGatewayMethod;
2714
+ if (typeof apiCallGatewayMethod === "function") return apiCallGatewayMethod(method, params);
2715
+ throw new Error("Gateway method invocation API unavailable in plugin context");
2716
+ };
2717
+ api.registerGatewayMethod(
2718
+ "squad.sessions.create",
2719
+ async (ctx) => {
2720
+ const { params, respond } = ctx;
2721
+ const sessionKey = params?.sessionKey;
2722
+ const agentId = params?.agentId;
2723
+ if (sessionKey !== void 0) {
2724
+ if (typeof sessionKey !== "string" || !sessionKey.trim()) {
2725
+ respond(false, { error: "Invalid 'sessionKey' parameter (must be a non-empty string when provided)" });
2726
+ return;
2727
+ }
2728
+ }
2729
+ if (agentId !== void 0) {
2730
+ if (typeof agentId !== "string" || !agentId.trim()) {
2731
+ respond(false, { error: "Invalid 'agentId' parameter (must be a non-empty string when provided)" });
2732
+ return;
2733
+ }
2734
+ }
2735
+ const createParams = {
2736
+ ...typeof sessionKey === "string" ? { sessionKey: sessionKey.trim() } : {},
2737
+ ...typeof agentId === "string" ? { agentId: agentId.trim() } : {}
2738
+ };
2739
+ try {
2740
+ const nativeResult = await callGateway(ctx, "sessions.create", createParams);
2741
+ const normalizedKey = extractSessionKey(nativeResult) ?? (typeof sessionKey === "string" ? sessionKey.trim() : null);
2742
+ if (!normalizedKey) {
2743
+ respond(false, {
2744
+ error: "Session creation succeeded but no session key was returned",
2745
+ nativeResult
2746
+ });
2747
+ return;
2748
+ }
2749
+ const nativeRecord = asRecord(nativeResult);
2750
+ respond(true, {
2751
+ ...nativeRecord ?? {},
2752
+ key: normalizedKey
2753
+ });
2754
+ } catch (err2) {
2755
+ const msg = err2 instanceof Error ? err2.message : String(err2);
2756
+ respond(false, {
2757
+ error: `Failed to create session: ${msg}`.slice(0, 500)
2758
+ });
2759
+ }
2760
+ }
2761
+ );
2762
+ }
2763
+
2593
2764
  // src/shared-api.ts
2594
2765
  var CORE_TOOLS = [
2595
2766
  "exec",
@@ -2642,6 +2813,7 @@ function registerSquadSharedApi(api, onFsChange) {
2642
2813
  registerVersionMethods(api);
2643
2814
  registerAgentMethods(api);
2644
2815
  registerQuestionMethods(api);
2816
+ registerSessionMethods(api);
2645
2817
  const invokeTool = async (tool, args) => {
2646
2818
  const executeFn = toolExecutors.get(tool);
2647
2819
  if (!executeFn) {
@@ -2703,7 +2875,7 @@ function squadAppPlugin(api) {
2703
2875
  const relayState = readRelayState();
2704
2876
  const relayEnabled = !!(relayState.claimToken || relayState.roomId);
2705
2877
  if (relayEnabled) {
2706
- const relayUrl = "wss://relay.squad.ceo";
2878
+ const relayUrl = process.env.OPENCLAW_SQUAD_RELAY_URL || process.env.SQUAD_RELAY_URL || "wss://relay.squad.ceo";
2707
2879
  startRelayClient(api, relayUrl);
2708
2880
  }
2709
2881
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squad-openclaw",
3
- "version": "2026.2.2201",
3
+ "version": "2026.2.2205",
4
4
  "description": "Entity registry, filesystem tools, and version management plugin for OpenClaw gateway",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",