@mobcode/openclaw-plugin 0.1.0 → 0.1.2

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 CHANGED
@@ -31,9 +31,11 @@
31
31
  - `mobcode.client.register`
32
32
  - `mobcode.client.actions.wait`
33
33
  - `mobcode.client.actions.ack`
34
- - tools:
34
+ - tools:
35
35
  - `present_artifact`
36
36
  - `mobcode_open`
37
+ - bundled skill:
38
+ - `mobcode-ui`
37
39
  - HTTP routes:
38
40
  - `/plugins/mobcode/health`
39
41
  - `/plugins/mobcode/config`
@@ -56,6 +58,7 @@
56
58
  - `providers.available` сейчас отдает MobCode static provider catalog, синхронизированный с мобильным клиентом, а не настоящий OpenClaw onboarding wizard source of truth.
57
59
  - `provider.models.list` получает модели по сети для конкретного provider, используя `api.runtime.config.loadConfig()` и `api.runtime.modelAuth.resolveApiKeyForProvider(...)`, не отдавая токены в mobileapp.
58
60
  - `present_artifact` и `mobcode_open` уже зарегистрированы как plugin tools; `mobcode_open` завершает tool call после client ack или timeout.
61
+ - вместе с plugin устанавливается bundled skill `mobcode-ui`, который подсказывает OpenClaw агенту, когда использовать `present_artifact` и `mobcode_open`.
59
62
  - real push delivery в FCM/APNs пока не сделана; есть только device registry и queue.
60
63
  - file diff flow лучше делать через интеграцию с официальным `diffs` plugin, а не изобретать свой формат.
61
64
  - mobile approvals построены поверх существующих `exec.approval.*` методов OpenClaw: plugin хранит state/history, а resolve по-прежнему идет через штатный RPC.
@@ -2,6 +2,9 @@
2
2
  "id": "mobcode",
3
3
  "name": "MobCode",
4
4
  "description": "MobCode mobile integration plugin for OpenClaw.",
5
+ "skills": [
6
+ "skills"
7
+ ],
5
8
  "configSchema": {
6
9
  "type": "object",
7
10
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobcode/openclaw-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MobCode integration plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,6 +16,7 @@
16
16
  "README.md",
17
17
  "index.js",
18
18
  "openclaw.plugin.json",
19
+ "skills",
19
20
  "src"
20
21
  ],
21
22
  "keywords": [
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: mobcode-ui
3
+ description: Guidance for using MobCode rich artifacts and app surfaces from OpenClaw.
4
+ ---
5
+
6
+ # MobCode UI
7
+
8
+ - You are operating inside MobCode, where the user can view rich artifacts and app surfaces directly in the mobile app.
9
+ - Prefer these tools when they make the result easier to inspect or act on.
10
+
11
+ ## Rich artifacts
12
+
13
+ - Use `present_artifact` when the result is easier to understand as a structured visual artifact than as a long plain-text reply.
14
+ - Good fits:
15
+ - dashboards
16
+ - charts
17
+ - status cards
18
+ - multi-section summaries
19
+ - review results with sections and actions
20
+ - Keep ordinary short answers as normal assistant text. Use artifacts only when structure or visual layout genuinely helps.
21
+
22
+ ### `present_artifact`
23
+
24
+ - Required argument: `artifact`
25
+ - Optional arguments:
26
+ - `item_id`
27
+ - `op` = `start` | `patch` | `done`
28
+ - The artifact document must include:
29
+ - `artifactId`
30
+ - `kind`
31
+ - `title`
32
+ - The artifact can also include:
33
+ - `summary`
34
+ - `initialScreenId`
35
+ - `screens`
36
+ - `actions`
37
+ - `media`
38
+ - `preview`
39
+ - `metadata`
40
+
41
+ Use `op="done"` for a one-shot artifact. Use `start` and `patch` only when you are progressively building the same artifact.
42
+
43
+ ## App surfaces
44
+
45
+ - Use `mobcode_open` when the user should directly inspect something in the MobCode app instead of reading a long explanation.
46
+ - Typical cases:
47
+ - open a local or forwarded web UI
48
+ - open a file at a relevant location
49
+ - jump to the terminal or another MobCode section
50
+
51
+ ### `mobcode_open`
52
+
53
+ - Required argument: `target`
54
+ - Optional argument: `params`
55
+
56
+ Supported targets:
57
+ - `browser_tab`
58
+ - requires `params.url`
59
+ - `file`
60
+ - requires `params.path`
61
+ - optional `params.start_line`
62
+ - optional `params.end_line`
63
+ - `terminal`
64
+ - optional `params.command`
65
+ - `docker`
66
+ - `kubernetes`
67
+ - `local_agent`
68
+ - `openclaw`
69
+ - these targets may include optional `params.section`
70
+
71
+ If the action is not necessary for the user to inspect or interact with something, prefer a normal text reply instead of `mobcode_open`.
72
+
73
+ ## Usage rules
74
+
75
+ - Prefer plain assistant text for simple answers.
76
+ - Use `present_artifact` for structured visual output.
77
+ - Use `mobcode_open` when the next best user action is to inspect or open something in the app.
78
+ - Do not invent unsupported targets or undocumented artifact fields.
@@ -1,7 +1,14 @@
1
- export function registerMobcodeGatewayMethods({ store, builders, featureMatrix, api }) {
1
+ export function registerMobcodeGatewayMethods({
2
+ store,
3
+ builders,
4
+ featureMatrix,
5
+ pluginMetadata,
6
+ api,
7
+ }) {
2
8
  api.registerGatewayMethod("mobcode.capabilities", async ({ respond }) => {
3
9
  respond(true, {
4
10
  ok: true,
11
+ plugin: pluginMetadata,
5
12
  features: featureMatrix(),
6
13
  });
7
14
  });
@@ -41,7 +41,7 @@ async function readSessionTranscriptMessagesFromInternals(sessionKey) {
41
41
  return Array.isArray(rawMessages) ? rawMessages : [];
42
42
  }
43
43
 
44
- async function readSessionMessagesThroughChatHistory(config, sessionKey) {
44
+ async function readSessionMessagesThroughChatHistory(config, sessionKey, logger) {
45
45
  const normalizedSessionKey = String(sessionKey ?? "").trim();
46
46
  if (!normalizedSessionKey) {
47
47
  return [];
@@ -56,20 +56,31 @@ async function readSessionMessagesThroughChatHistory(config, sessionKey) {
56
56
  clientDisplayName: "MobCode History Backfill",
57
57
  });
58
58
  const messages = payload?.messages;
59
+ logger?.info?.(
60
+ `[mobcode-history] chat.history session=${normalizedSessionKey} messages=${Array.isArray(messages) ? messages.length : 0}`,
61
+ );
59
62
  return Array.isArray(messages) ? messages : [];
60
63
  }
61
64
 
62
- export async function readSessionTranscriptMessages(config, sessionKey) {
65
+ export async function readSessionTranscriptMessages(config, sessionKey, logger) {
63
66
  try {
64
67
  const gatewayMessages = await readSessionMessagesThroughChatHistory(
65
68
  config,
66
69
  sessionKey,
70
+ logger,
67
71
  );
68
72
  if (gatewayMessages.length > 0) {
69
73
  return gatewayMessages;
70
74
  }
71
- } catch {
75
+ } catch (error) {
76
+ logger?.warn?.(
77
+ `[mobcode-history] chat.history failed session=${String(sessionKey ?? "").trim()}: ${error instanceof Error ? error.message : String(error)}`,
78
+ );
72
79
  // Fall back to adjacent-source runtime imports for development checkouts.
73
80
  }
74
- return readSessionTranscriptMessagesFromInternals(sessionKey);
81
+ const internalMessages = await readSessionTranscriptMessagesFromInternals(sessionKey);
82
+ logger?.info?.(
83
+ `[mobcode-history] internal fallback session=${String(sessionKey ?? "").trim()} messages=${internalMessages.length}`,
84
+ );
85
+ return internalMessages;
75
86
  }
@@ -9,6 +9,7 @@ import {
9
9
  tryBuildAvailableProvidersPayload,
10
10
  tryBuildProviderModelsPayload,
11
11
  } from "./openclaw-introspection.js";
12
+ import { readPluginMetadata } from "./plugin-metadata.js";
12
13
  import { readSessionTranscriptMessages } from "./openclaw-session-reader.js";
13
14
  import { registerMobcodeRuntimeObservers } from "./runtime-events.js";
14
15
  import { MobcodeStateStore } from "./state-store.js";
@@ -22,8 +23,10 @@ export function createMobcodePluginDefinition() {
22
23
  register(api) {
23
24
  const pluginConfig = api.pluginConfig ?? {};
24
25
  const stateDir = api.runtime.state.resolveStateDir();
26
+ const pluginMetadata = readPluginMetadata();
25
27
  const store = new MobcodeStateStore({
26
28
  rootDir: path.join(stateDir, "plugins", "mobcode"),
29
+ logger: api.logger,
27
30
  });
28
31
 
29
32
  const builders = {
@@ -33,7 +36,11 @@ export function createMobcodePluginDefinition() {
33
36
  await tryBuildProviderModelsPayload(api, providerId),
34
37
  ensureSessionMessages: async (sessionKey) => {
35
38
  await store.ensureSessionIndexed(sessionKey, async () =>
36
- readSessionTranscriptMessages(api.runtime.config.loadConfig(), sessionKey),
39
+ readSessionTranscriptMessages(
40
+ api.runtime.config.loadConfig(),
41
+ sessionKey,
42
+ api.logger,
43
+ ),
37
44
  );
38
45
  },
39
46
  };
@@ -43,6 +50,7 @@ export function createMobcodePluginDefinition() {
43
50
  store,
44
51
  builders,
45
52
  featureMatrix: listFeatureMatrix,
53
+ pluginMetadata,
46
54
  });
47
55
  registerMobcodeHttpRoutes({
48
56
  api,
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs";
2
+
3
+ const DEFAULT_PLUGIN_METADATA = Object.freeze({
4
+ id: "mobcode",
5
+ packageName: "@mobcode/openclaw-plugin",
6
+ version: "0.0.0",
7
+ });
8
+
9
+ let cachedPluginMetadata = null;
10
+
11
+ export function readPluginMetadata() {
12
+ if (cachedPluginMetadata != null) {
13
+ return cachedPluginMetadata;
14
+ }
15
+
16
+ try {
17
+ const packageJsonPath = new URL("../package.json", import.meta.url);
18
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
19
+ const packageName =
20
+ typeof parsed?.name === "string" ? parsed.name.trim() : "";
21
+ const version = typeof parsed?.version === "string" ? parsed.version.trim() : "";
22
+ cachedPluginMetadata = Object.freeze({
23
+ id: DEFAULT_PLUGIN_METADATA.id,
24
+ packageName: packageName.length > 0
25
+ ? packageName
26
+ : DEFAULT_PLUGIN_METADATA.packageName,
27
+ version: version.length > 0 ? version : DEFAULT_PLUGIN_METADATA.version,
28
+ });
29
+ } catch (_) {
30
+ cachedPluginMetadata = DEFAULT_PLUGIN_METADATA;
31
+ }
32
+
33
+ return cachedPluginMetadata;
34
+ }
@@ -596,8 +596,9 @@ function normalizeApprovalStatus(status, decision) {
596
596
  }
597
597
 
598
598
  export class MobcodeStateStore {
599
- constructor({ rootDir }) {
599
+ constructor({ rootDir, logger = null }) {
600
600
  this.rootDir = rootDir;
601
+ this.logger = logger;
601
602
  this.databasePath = path.join(rootDir, "mobcode.sqlite");
602
603
  this.database = null;
603
604
  this.clientActionWaiters = new Map();
@@ -803,9 +804,16 @@ export class MobcodeStateStore {
803
804
  return;
804
805
  }
805
806
  const existing = this._db()
806
- .prepare(`SELECT session_key FROM indexed_sessions WHERE session_key=? LIMIT 1`)
807
+ .prepare(
808
+ `SELECT session_key, last_backfill_at
809
+ FROM indexed_sessions
810
+ WHERE session_key=?
811
+ LIMIT 1`,
812
+ )
807
813
  .get(normalizedSessionKey);
808
- if (existing) {
814
+ const alreadyBackfilled =
815
+ typeof existing?.last_backfill_at === "string" && existing.last_backfill_at.trim().length > 0;
816
+ if (alreadyBackfilled) {
809
817
  return;
810
818
  }
811
819
  const messages = Array.isArray(loader) ? loader : await loader();
@@ -918,6 +926,9 @@ export class MobcodeStateStore {
918
926
  });
919
927
 
920
928
  transaction(messages);
929
+ this.logger?.info?.(
930
+ `[mobcode-store] indexed session=${normalizedSessionKey} messages=${messages.length}`,
931
+ );
921
932
  }
922
933
 
923
934
  async appendMessage(update) {