openclaw-app 1.1.6 → 1.1.7

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/index.ts CHANGED
@@ -930,12 +930,51 @@ async function handleRpc(ctx: any, accountId: string, msg: any): Promise<boolean
930
930
  agentsSample: Array.isArray(agentsList) ? agentsList.slice(0, 3) : agentsList,
931
931
  });
932
932
  } else if (method === "runtime.inspect.deep") {
933
- // Inspect sub-objects to find agents.create capability
934
- const systemKeys = runtime?.system ? Object.keys(runtime.system) : [];
935
- const toolsKeys = runtime?.tools ? Object.keys(runtime.tools) : [];
936
- const stateKeys = runtime?.state ? Object.keys(runtime.state) : [];
937
- const agentsApiKeys = runtime?.agents ? Object.keys(runtime.agents) : [];
938
- await sendRpcReply({ systemKeys, toolsKeys, stateKeys, agentsApiKeys });
933
+ // Deep inspection to discover skills API surface
934
+ const allRuntimeKeys = runtime ? Object.keys(runtime) : [];
935
+
936
+ // Walk every top-level key and record its type + sub-keys (if object/function)
937
+ const runtimeShape: Record<string, any> = {};
938
+ for (const k of allRuntimeKeys) {
939
+ try {
940
+ const v = runtime[k];
941
+ const t = typeof v;
942
+ if (t === 'function') {
943
+ runtimeShape[k] = 'function';
944
+ } else if (v && t === 'object') {
945
+ runtimeShape[k] = Object.keys(v).slice(0, 20);
946
+ } else {
947
+ runtimeShape[k] = t;
948
+ }
949
+ } catch (_) {
950
+ runtimeShape[k] = 'error';
951
+ }
952
+ }
953
+
954
+ // Specifically probe skills-related paths
955
+ const skillsProbe: Record<string, any> = {};
956
+ const candidates = ['skills', 'skillManager', 'skillLoader', 'skillRegistry', 'pluginSkills'];
957
+ for (const c of candidates) {
958
+ if (runtime?.[c]) {
959
+ skillsProbe[c] = typeof runtime[c] === 'object'
960
+ ? Object.keys(runtime[c])
961
+ : typeof runtime[c];
962
+ }
963
+ }
964
+
965
+ // Also probe config.skills
966
+ let cfgSkills: any = null;
967
+ try {
968
+ const cfg = runtime.config.loadConfig();
969
+ cfgSkills = {
970
+ topKeys: Object.keys(cfg?.skills ?? {}),
971
+ entriesKeys: Object.keys(cfg?.skills?.entries ?? {}),
972
+ entriesSample: Object.entries(cfg?.skills?.entries ?? {}).slice(0, 5)
973
+ .map(([k, v]: [string, any]) => ({ name: k, enabled: v?.enabled })),
974
+ };
975
+ } catch (_) {}
976
+
977
+ await sendRpcReply({ runtimeShape, skillsProbe, cfgSkills });
939
978
  } else if (method === "agents.list") {
940
979
  const cfg = runtime.config.loadConfig();
941
980
  // cfg.agents structure: { defaults: {...}, list: [{id, name, workspace, ...}], ... }
@@ -958,9 +997,10 @@ async function handleRpc(ctx: any, accountId: string, msg: any): Promise<boolean
958
997
  })).filter((a: any) => a.id);
959
998
  await sendRpcReply({ agents: list });
960
999
  } else if (method === "sessions.list") {
961
- const cfg = runtime.config.loadConfig();
962
- const sessions = runtime.session?.listSessions?.({ cfg, accountId }) ?? [];
963
- await sendRpcReply({ sessions });
1000
+ // Sessions are stored locally in the mobile app's Drift DB.
1001
+ // The Gateway/CLI has no sessions API we can call reliably.
1002
+ // Return empty — session_provider.dart falls back to local DB which is the source of truth.
1003
+ await sendRpcReply({ sessions: [] });
964
1004
  } else if (method === "agents.create") {
965
1005
  // Use CLI: openclaw agents add <name> --workspace <path> [--model <model>]
966
1006
  const name = params.name as string | undefined;
@@ -980,6 +1020,119 @@ async function handleRpc(ctx: any, accountId: string, msg: any): Promise<boolean
980
1020
  await sendRpcReply({ name, workspace, output: result.stdout ?? '' });
981
1021
  }
982
1022
  }
1023
+ } else if (method === "tools.list") {
1024
+ // Use `openclaw skills list --json` CLI — most reliable source for loaded skills.
1025
+ // Falls back to fs scan of ~/.openclaw/skills if CLI is unavailable.
1026
+ let tools: any[] = [];
1027
+
1028
+ try {
1029
+ // Try object form first; fall back to string form (same as agents.create)
1030
+ let cliResult: any;
1031
+ try {
1032
+ cliResult = await runtime.system.runCommandWithTimeout(
1033
+ { cmd: 'openclaw', args: ['skills', 'list', '--json'] },
1034
+ 10000
1035
+ );
1036
+ } catch (_) {
1037
+ cliResult = await runtime.system.runCommandWithTimeout(
1038
+ 'openclaw skills list --json',
1039
+ 10000
1040
+ );
1041
+ }
1042
+ if (cliResult.exitCode === 0 && cliResult.stdout?.trim()) {
1043
+ const parsed = JSON.parse(cliResult.stdout.trim());
1044
+ const raw: any[] = Array.isArray(parsed) ? parsed
1045
+ : Array.isArray(parsed?.skills) ? parsed.skills
1046
+ : parsed?.data ?? [];
1047
+ tools = raw
1048
+ .filter((s: any) => s['user-invocable'] !== false && s['user-invocable'] !== 'false')
1049
+ .map((s: any) => ({
1050
+ name: s.name ?? s.id ?? '',
1051
+ description: s.description ?? s.desc ?? '',
1052
+ category: s.category ?? '',
1053
+ userInvocable: true,
1054
+ }))
1055
+ .filter((t: any) => t.name);
1056
+ }
1057
+ } catch (_) {}
1058
+
1059
+ // Fallback: scan ~/.openclaw for all skills/ subdirs, deduplicated
1060
+ if (tools.length === 0) {
1061
+ try {
1062
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1063
+ const fs = (globalThis as any).require?.('fs') ?? require('fs');
1064
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1065
+ const path = (globalThis as any).require?.('path') ?? require('path');
1066
+ const cfg = runtime.config.loadConfig();
1067
+ const proc = (globalThis as any).process;
1068
+ const home = proc?.env?.HOME ?? proc?.env?.USERPROFILE ?? '~';
1069
+ const ocRoot = `${home}/.openclaw`;
1070
+
1071
+ // Collect all skills/ directories under ~/.openclaw recursively (depth 2)
1072
+ const skillDirs: string[] = [];
1073
+ const addSkillsDir = (dir: string) => {
1074
+ try {
1075
+ if (fs.existsSync(dir)) skillDirs.push(dir);
1076
+ } catch (_) {}
1077
+ };
1078
+
1079
+ // ~/.openclaw/skills
1080
+ addSkillsDir(`${ocRoot}/skills`);
1081
+
1082
+ // ~/.openclaw/*/skills (workspace dirs one level deep)
1083
+ try {
1084
+ for (const entry of fs.readdirSync(ocRoot)) {
1085
+ const sub = path.join(ocRoot, entry, 'skills');
1086
+ addSkillsDir(sub);
1087
+ }
1088
+ } catch (_) {}
1089
+
1090
+ // Extra dirs from config
1091
+ for (const d of (cfg?.skills?.load?.extraDirs ?? [])) addSkillsDir(d);
1092
+
1093
+ const seen = new Set<string>();
1094
+ for (const dir of skillDirs) {
1095
+ let entries: string[];
1096
+ try { entries = fs.readdirSync(dir); } catch (_) { continue; }
1097
+ for (const entry of entries) {
1098
+ const skillFile = path.join(dir, entry, 'SKILL.md');
1099
+ let content: string;
1100
+ try { content = fs.readFileSync(skillFile, 'utf8'); } catch (_) { continue; }
1101
+ const fm = _parseSkillFrontmatter(content);
1102
+ const name: string = fm.name ?? entry;
1103
+ if (!name || seen.has(name)) continue;
1104
+ seen.add(name);
1105
+ if (fm['user-invocable'] === false || fm['user-invocable'] === 'false') continue;
1106
+ if (cfg?.skills?.entries?.[name]?.enabled === false) continue;
1107
+ tools.push({ name, description: fm.description ?? '', category: '', userInvocable: true });
1108
+ }
1109
+ }
1110
+ } catch (_) {}
1111
+ }
1112
+
1113
+ await sendRpcReply({ tools });
1114
+ } else if (method === "tools.invoke") {
1115
+ // Invoke a skill by injecting a /skill <name> command into the chat session
1116
+ const toolName = params.name as string | undefined;
1117
+ const input = (params.params?.input ?? params.input ?? '') as string;
1118
+ if (!toolName) {
1119
+ await sendRpcReply(null, "tools.invoke: missing required param 'name'");
1120
+ } else {
1121
+ try {
1122
+ // Send /skill <name> [input] as a chat message to the current session
1123
+ const cmd = input ? `/skill ${toolName} ${input}` : `/skill ${toolName}`;
1124
+ const wireSessionKey = replySessionKey ?? 'rpc';
1125
+ await handleInbound(ctx, accountId, {
1126
+ type: 'message',
1127
+ content: cmd,
1128
+ sessionKey: wireSessionKey,
1129
+ chatSessionKey: wireSessionKey,
1130
+ });
1131
+ await sendRpcReply({ dispatched: true, command: cmd });
1132
+ } catch (e: any) {
1133
+ await sendRpcReply(null, `tools.invoke '${toolName}' error: ${e}`);
1134
+ }
1135
+ }
983
1136
  } else {
984
1137
  await sendRpcReply(null, `Unknown RPC method: ${method}`);
985
1138
  }
@@ -1208,4 +1361,29 @@ export default function register(api: any) {
1208
1361
  });
1209
1362
 
1210
1363
  api.logger?.info?.("[openclaw-app] Plugin registered");
1364
+ }
1365
+
1366
+ // ── Helpers ──────────────────────────────────────────────────────────────────
1367
+
1368
+ /**
1369
+ * Parse YAML frontmatter from a SKILL.md string.
1370
+ * Returns a flat key→value map for simple scalar fields.
1371
+ * Handles: name, description, user-invocable, disable-model-invocation, command-dispatch.
1372
+ */
1373
+ function _parseSkillFrontmatter(content: string): Record<string, any> {
1374
+ const result: Record<string, any> = {};
1375
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
1376
+ if (!match) return result;
1377
+ for (const line of match[1].split('\n')) {
1378
+ const colon = line.indexOf(':');
1379
+ if (colon === -1) continue;
1380
+ const key = line.slice(0, colon).trim();
1381
+ const val = line.slice(colon + 1).trim();
1382
+ if (!key) continue;
1383
+ // Parse booleans
1384
+ if (val === 'true') result[key] = true;
1385
+ else if (val === 'false') result[key] = false;
1386
+ else result[key] = val;
1387
+ }
1388
+ return result;
1211
1389
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-app",
3
3
  "name": "OpenClaw App",
4
- "version": "1.1.6",
4
+ "version": "1.1.7",
5
5
  "description": "Mobile app channel for OpenClaw — chat via the OpenClaw App app through a Cloudflare Worker relay.",
6
6
  "channels": [
7
7
  "openclaw-app"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-app",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "OpenClaw App channel plugin — relay bridge for the OpenClaw App app",
5
5
  "main": "index.ts",
6
6
  "type": "module",