@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 +4 -1
- package/openclaw.plugin.json +3 -0
- package/package.json +2 -1
- package/skills/mobcode-ui/SKILL.md +78 -0
- package/src/gateway-methods.js +8 -1
- package/src/openclaw-session-reader.js +15 -4
- package/src/plugin-definition.js +9 -1
- package/src/plugin-metadata.js +34 -0
- package/src/state-store.js +14 -3
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
|
-
|
|
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.
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobcode/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
package/src/gateway-methods.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
export function registerMobcodeGatewayMethods({
|
|
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
|
-
|
|
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
|
}
|
package/src/plugin-definition.js
CHANGED
|
@@ -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(
|
|
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
|
+
}
|
package/src/state-store.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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) {
|