@clawling/clawchat-plugin-openclaw 2026.10.25-1 → 2026.10.27-1
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/dist/src/runtime.js +2 -3
- package/dist/src/skill-update.js +1 -1
- package/dist/src/storage.js +39 -21
- package/dist/src/widget-guide.js +66 -8
- package/package.json +1 -1
- package/skills/clawchat-core/SKILL.md +67 -9
- package/skills/manifest.json +6 -6
- package/src/runtime.ts +2 -3
- package/src/skill-update.ts +1 -1
- package/src/storage.ts +56 -39
- package/src/widget-guide.ts +66 -8
package/dist/src/runtime.js
CHANGED
|
@@ -4630,9 +4630,8 @@ async function runOpenclawClawlingGateway(params, lifecycle) {
|
|
|
4630
4630
|
// Synchronous and fire-and-forget on purpose: there is no ack to send (the
|
|
4631
4631
|
// frame is BestEffort and non-ackable).
|
|
4632
4632
|
//
|
|
4633
|
-
// docs/client-integration.md §9.8 describes the permanent tombstone and
|
|
4634
|
-
//
|
|
4635
|
-
// transaction. See that section before changing recall/claim ordering.
|
|
4633
|
+
// docs/client-integration.md §9.8 describes the permanent tombstone and the
|
|
4634
|
+
// BEGIN IMMEDIATE transactions that serialize recall and claim.
|
|
4636
4635
|
client.on("message:recall", (env) => {
|
|
4637
4636
|
const target = recallTargetMessageId(env.payload);
|
|
4638
4637
|
if (!target) {
|
package/dist/src/skill-update.js
CHANGED
|
@@ -65,7 +65,7 @@ export const OFFICIAL_SKILLS_BASE = "https://raw.githubusercontent.com/clawling/
|
|
|
65
65
|
* in the install-cli repo, bump this constant, ship it. `liveware-sample.ts`
|
|
66
66
|
* imports the same ref, so the `livewares` tree at that tag is pinned too.
|
|
67
67
|
*/
|
|
68
|
-
export const DEFAULT_SKILLS_REF = "skills-v1.
|
|
68
|
+
export const DEFAULT_SKILLS_REF = "skills-v1.19.0";
|
|
69
69
|
/** Refuse to treat an absurdly large response as a skill file (defence in depth). */
|
|
70
70
|
export const MAX_SKILL_BYTES = 256 * 1024;
|
|
71
71
|
/** This adapter's host target inside `skills/manifest.json`. */
|
package/dist/src/storage.js
CHANGED
|
@@ -1030,18 +1030,28 @@ export class ClawChatStore {
|
|
|
1030
1030
|
}
|
|
1031
1031
|
claimMessageOnce(input) {
|
|
1032
1032
|
return this.write(() => {
|
|
1033
|
-
// docs/client-integration.md §9.8:
|
|
1034
|
-
//
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1033
|
+
// docs/client-integration.md §9.8: the tombstone check and the claim
|
|
1034
|
+
// insert run in one BEGIN IMMEDIATE transaction.
|
|
1035
|
+
const db = this.requireDb();
|
|
1036
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1037
|
+
try {
|
|
1038
|
+
if (input.messageId && this.isMessageRecalled(input)) {
|
|
1039
|
+
db.exec("COMMIT");
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
const result = db
|
|
1043
|
+
.prepare(`INSERT OR IGNORE INTO clawchat_messages(
|
|
1044
|
+
platform, account_id, kind, direction, event_type, trace_id, chat_id,
|
|
1045
|
+
message_id, text, raw_json, created_at, send_status
|
|
1046
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1047
|
+
.run(input.platform, input.accountId, input.kind, input.direction, input.eventType, input.traceId ?? null, input.chatId ?? null, input.messageId ?? null, input.text ?? null, toJson(input.raw), input.createdAt ?? Date.now(), input.direction === "outbound" ? "pending" : null);
|
|
1048
|
+
db.exec("COMMIT");
|
|
1049
|
+
return result.changes > 0;
|
|
1050
|
+
}
|
|
1051
|
+
catch (error) {
|
|
1052
|
+
db.exec("ROLLBACK");
|
|
1053
|
+
throw error;
|
|
1054
|
+
}
|
|
1045
1055
|
});
|
|
1046
1056
|
}
|
|
1047
1057
|
markMessageFailed(input) {
|
|
@@ -1144,8 +1154,8 @@ export class ClawChatStore {
|
|
|
1144
1154
|
* account was paired, or one dropped by the inbound filter). `null` means the
|
|
1145
1155
|
* store is unavailable.
|
|
1146
1156
|
*
|
|
1147
|
-
* Records the permanent recall tombstone and
|
|
1148
|
-
* see docs/client-integration.md §9.8
|
|
1157
|
+
* Records the permanent recall tombstone and deletes ledger rows in one
|
|
1158
|
+
* BEGIN IMMEDIATE transaction; see docs/client-integration.md §9.8.
|
|
1149
1159
|
* Returns the number of ledger rows deleted (0 when the recall
|
|
1150
1160
|
* arrived before the message did — the tombstone still stands and the later
|
|
1151
1161
|
* ingest is refused by {@link claimMessageOnce}).
|
|
@@ -1157,13 +1167,21 @@ export class ClawChatStore {
|
|
|
1157
1167
|
return 0;
|
|
1158
1168
|
return this.write(() => {
|
|
1159
1169
|
const db = this.requireDb();
|
|
1160
|
-
db.
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1170
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1171
|
+
try {
|
|
1172
|
+
db.prepare(`INSERT OR IGNORE INTO recalled_messages(platform, account_id, message_id, recalled_at)
|
|
1173
|
+
VALUES (?, ?, ?, ?)`).run(input.platform, input.accountId, input.messageId, input.recalledAt ?? Date.now());
|
|
1174
|
+
const result = db
|
|
1175
|
+
.prepare(`DELETE FROM clawchat_messages
|
|
1176
|
+
WHERE platform = ? AND account_id = ? AND message_id = ?`)
|
|
1177
|
+
.run(input.platform, input.accountId, input.messageId);
|
|
1178
|
+
db.exec("COMMIT");
|
|
1179
|
+
return Number(result.changes);
|
|
1180
|
+
}
|
|
1181
|
+
catch (error) {
|
|
1182
|
+
db.exec("ROLLBACK");
|
|
1183
|
+
throw error;
|
|
1184
|
+
}
|
|
1167
1185
|
});
|
|
1168
1186
|
}
|
|
1169
1187
|
/**
|
package/dist/src/widget-guide.js
CHANGED
|
@@ -1,30 +1,87 @@
|
|
|
1
1
|
export const WIDGET_V1_GUIDE = `## Home-screen widgets (widget/v1)
|
|
2
2
|
|
|
3
|
-
A widget is a small card on your partner's phone home screen. You write it with \`clawchat_widget_send\`; their taps come back to you as ordinary messages.
|
|
3
|
+
A widget is a small card on your partner's phone home screen. You write it with \`clawchat_widget_send\`; their taps come back to you as ordinary messages. It only ever goes to your private chat with them.
|
|
4
|
+
|
|
5
|
+
### When to send
|
|
4
6
|
|
|
5
7
|
- Send one only after your partner asked for a widget — a recipe card, or their own words.
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
- If they keep asking you for the same thing (a price, a list, a count), you may offer a widget once. Make it only after they say yes. Never offer the same one twice.
|
|
9
|
+
|
|
10
|
+
### When to update
|
|
11
|
+
|
|
12
|
+
Send a new snapshot in the same turn whenever what it shows has changed:
|
|
13
|
+
|
|
14
|
+
- they tapped or typed on it (a \`[widget <id>] …\` message);
|
|
15
|
+
- they changed the same data in chat: "remind me to buy milk tomorrow" also goes onto the to-do widget;
|
|
16
|
+
- a new day began: "today", "tomorrow" or "N days left" in your last snapshot are now wrong. Send it again in your first turn of the day. If it counts days, schedule yourself a turn early every morning for it.
|
|
17
|
+
|
|
18
|
+
Nothing changed: send nothing. Every message is the full snapshot, never a diff; the latest one per \`id\` wins. At most one per \`id\` a minute. Data that changes by itself (prices, weather) belongs in \`source\`, not in repeated snapshots.
|
|
19
|
+
|
|
20
|
+
### Design
|
|
21
|
+
|
|
22
|
+
- One widget answers one question. \`title\`: a few words. Never repeat it, the \`badge\` or the big number elsewhere; no "tap + to add" hints.
|
|
23
|
+
- Write \`small\`, \`medium\` and \`large\`, each from one layout below, with the same main figure. A missing size is stretched from another and looks broken.
|
|
24
|
+
- Name things in every size: a figure alone says nothing. Every text is one line. Per line: small about 7 Chinese characters (12 Latin), medium and large about 17 (30). Write it short; never rely on "…".
|
|
25
|
+
- Height is tight. \`small\` and \`medium\` fit exactly one of: \`[caption; number title; caption]\`, 4 lines of text, or a \`list\` of 3. A \`button\` counts as 2 lines; with a \`source\`, one line less (its footer). Anything taller is not drawn.
|
|
26
|
+
- \`large\` is twice as tall as \`medium\`: fill it with what medium had no room for (more items, a breakdown, the next ones). Never the medium tree alone, never a figure twice.
|
|
27
|
+
- One big \`number\` (\`style: title\`) per size; in \`small\` at most 4 characters ("81737" is 5: \`style: body\` there). \`unit\` is a unit only ("km", "kg", "%"); labels go in a \`caption\`.
|
|
28
|
+
- Supporting lines: \`caption\` with \`tone: secondary\`. Columns side by side get the same lines, so their tops line up. \`tone: accent\` in one place at most, only for what is urgent. An \`icon\` only when it tells items apart.
|
|
29
|
+
- A \`list\` holds things of one kind: write all of them, we show what fits and "N more" — only when the \`list\` is the whole size, not inside a \`column\`. Buttons and sentences never go in a \`list\`; only its first row may have a big number.
|
|
30
|
+
- \`action: capture\` only when they add items by typing (to-dos, expenses); then no \`button\` doing the same. A \`button\` only for one tap that changes something (check in, add a cup, draw again).
|
|
31
|
+
- \`badge\`: only the number of open items, and then no big number saying the same.
|
|
32
|
+
- Nothing that goes stale: dates ("10/24") and clock times ("last 8:20"), never "today", "tomorrow" or "50 min ago" (not in the \`title\` either), even if they ask "how long since": a widget has no running clock. A count for one day carries its date.
|
|
33
|
+
- Nothing left: an empty \`list\`; we draw the empty state.
|
|
34
|
+
- Every text in your partner's language, their items in their own words.
|
|
35
|
+
|
|
36
|
+
### Layouts
|
|
37
|
+
|
|
38
|
+
Pick by what it shows. \`[a; b]\` is a \`column\`, \`(a | b)\` a \`row\`.
|
|
39
|
+
|
|
40
|
+
1. One figure (spent this month, cups of water, km this week):
|
|
41
|
+
small \`[caption label; number title + unit; caption context]\` (with a \`source\`: drop the last caption), or with a tap \`[number title + unit; button]\`;
|
|
42
|
+
medium \`([caption label; number title + unit; caption context] | X)\`, X is one thing: a \`ring\` for a goal, a \`button\`, or \`[caption; number body]\`. Never two things stacked beside it;
|
|
43
|
+
large \`[the medium tree; list of up to 5 (text grow | number body)]\`: a breakdown or the last entries.
|
|
44
|
+
2. Several things, each with a figure or a time (prices, rates, books, a schedule, countdowns): every size is a \`list\`, one row per thing, the nearest or most important first.
|
|
45
|
+
first row \`(text name grow | number title + unit)\`, the rest \`(text name grow | number body + unit)\` or \`(text name grow | caption time)\`;
|
|
46
|
+
small: the same rows with short names. A \`list\` grows with taller widgets and counts "N more"; a \`column\` does not.
|
|
47
|
+
3. Things to tick (to-dos, shopping, habits): see the document below.
|
|
48
|
+
4. Words (a fortune, a word of the day): one \`column\`, one text per line, never side by side in a \`row\`.
|
|
49
|
+
small \`[text title; caption; caption]\`; medium \`[text title; text body; caption; caption]\`, or \`[(text title grow | button); text body; caption]\` with a tap; large: up to 4 more \`caption\` lines. Split long sentences.
|
|
50
|
+
|
|
51
|
+
### Removing one
|
|
52
|
+
|
|
53
|
+
Asked to remove a widget: send \`{"v": 1, "id": "todo", "removed": true}\` with its \`id\`. It leaves their widget list; a copy on the home screen shows "deleted" until they long-press it away. \`[widget todo] removed\` means they deleted it themselves. Either way, stop sending it. A later snapshot with that \`id\` brings it back, so send one only if they ask again.
|
|
9
54
|
|
|
10
55
|
### Document
|
|
11
56
|
|
|
12
57
|
\`\`\`json
|
|
13
|
-
{"v": 1, "id": "todo", "title": "To-do", "badge":
|
|
58
|
+
{"v": 1, "id": "todo", "title": "To-do", "badge": 2,
|
|
14
59
|
"action": {"do": "capture"},
|
|
60
|
+
"small": {"type": "list", "items": [
|
|
61
|
+
{"type": "row", "children": [
|
|
62
|
+
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
63
|
+
{"type": "text", "text": "Pay rent", "grow": true}]},
|
|
64
|
+
{"type": "row", "children": [
|
|
65
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
66
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]},
|
|
15
67
|
"medium": {"type": "list", "items": [
|
|
16
68
|
{"type": "row", "children": [
|
|
17
69
|
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
18
70
|
{"type": "text", "text": "Pay rent", "grow": true},
|
|
19
|
-
{"type": "text", "text": "
|
|
71
|
+
{"type": "text", "text": "10/24", "style": "caption", "tone": "accent"}]},
|
|
72
|
+
{"type": "row", "children": [
|
|
73
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
74
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]}}
|
|
20
75
|
\`\`\`
|
|
21
76
|
|
|
77
|
+
\`large\`: the same \`list\` as \`medium\`. Always list every item; we show what fits and "N more".
|
|
78
|
+
|
|
22
79
|
- \`v\`: 1. \`id\`: \`^[a-z0-9][a-z0-9_-]{0,31}$\`, the same across updates. \`title\`: up to 40 characters.
|
|
23
80
|
- \`badge\` (optional): an integer 0–9999, or up to 12 characters.
|
|
24
81
|
- \`action\` (optional, top right): \`capture\`, \`chat\` or \`liveware\`.
|
|
25
82
|
- \`small\` / \`medium\` / \`large\`: one block tree per size, at least one. A missing size borrows \`medium\`, then \`large\`, then \`small\`. A medium list shows about 3 rows.
|
|
26
83
|
- At most 16 KiB of UTF-8. Unknown fields and block types are skipped.
|
|
27
|
-
-
|
|
84
|
+
- Colours, fonts and spacing are ours; you choose the structure.
|
|
28
85
|
|
|
29
86
|
### Blocks
|
|
30
87
|
|
|
@@ -59,7 +116,7 @@ A widget is a small card on your partner's phone home screen. You write it with
|
|
|
59
116
|
"source": {"url": "https://…", "every": "30m", "fields": {"price": "data.price"}}
|
|
60
117
|
\`\`\`
|
|
61
118
|
|
|
62
|
-
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h.
|
|
119
|
+
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h. A field is a dot path into that JSON, array items by number (\`data.items.0.price\`); each step is letters, digits or \`_\` only, so a key like \`xnet-1\` cannot be read. Field names: lowercase letters, digits and \`_\`; every \`{name}\` you write must be one of them. Write \`{price}\` in \`text\` or \`number\` where the value goes: a value you type in yourself goes stale. Your partner sees the domain.
|
|
63
120
|
|
|
64
121
|
### What comes back
|
|
65
122
|
|
|
@@ -73,6 +130,7 @@ Each tap or entry arrives as an ordinary message with one line for you:
|
|
|
73
130
|
[widget todo] pressed "Refresh" (b1)
|
|
74
131
|
[widget todo] capture: buy milk
|
|
75
132
|
[widget todo] resend
|
|
133
|
+
[widget todo] removed
|
|
76
134
|
\`\`\`
|
|
77
135
|
|
|
78
136
|
They can arrive late and several at once: apply each in order, they are not duplicates. Then send one new full snapshot. \`resend\` means a device lost the widget: send the latest snapshot again.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clawchat-core
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.13.0
|
|
4
4
|
description: Use when a request involves ClawChat profile, friends, user search, moments/dynamics, comments, reactions, avatar, media, memory, output visibility, read-only conversation lookup, sending an image, video, file, or voice/audio clip into a conversation, managing the owner's other agents or groups, updating the owner's home-screen widgets, or plugin install/update/activation.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -196,31 +196,88 @@ Do not invent invite codes, tokens, moment ids, comment ids, user ids, emoji rea
|
|
|
196
196
|
|
|
197
197
|
## Home-screen widgets (widget/v1)
|
|
198
198
|
|
|
199
|
-
A widget is a small card on your partner's phone home screen. You write it with `clawchat_widget_send`; their taps come back to you as ordinary messages.
|
|
199
|
+
A widget is a small card on your partner's phone home screen. You write it with `clawchat_widget_send`; their taps come back to you as ordinary messages. It only ever goes to your private chat with them.
|
|
200
|
+
|
|
201
|
+
### When to send
|
|
200
202
|
|
|
201
203
|
- Send one only after your partner asked for a widget — a recipe card, or their own words.
|
|
202
|
-
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
- If they keep asking you for the same thing (a price, a list, a count), you may offer a widget once. Make it only after they say yes. Never offer the same one twice.
|
|
205
|
+
|
|
206
|
+
### When to update
|
|
207
|
+
|
|
208
|
+
Send a new snapshot in the same turn whenever what it shows has changed:
|
|
209
|
+
|
|
210
|
+
- they tapped or typed on it (a `[widget <id>] …` message);
|
|
211
|
+
- they changed the same data in chat: "remind me to buy milk tomorrow" also goes onto the to-do widget;
|
|
212
|
+
- a new day began: "today", "tomorrow" or "N days left" in your last snapshot are now wrong. Send it again in your first turn of the day. If it counts days, schedule yourself a turn early every morning for it.
|
|
213
|
+
|
|
214
|
+
Nothing changed: send nothing. Every message is the full snapshot, never a diff; the latest one per `id` wins. At most one per `id` a minute. Data that changes by itself (prices, weather) belongs in `source`, not in repeated snapshots.
|
|
215
|
+
|
|
216
|
+
### Design
|
|
217
|
+
|
|
218
|
+
- One widget answers one question. `title`: a few words. Never repeat it, the `badge` or the big number elsewhere; no "tap + to add" hints.
|
|
219
|
+
- Write `small`, `medium` and `large`, each from one layout below, with the same main figure. A missing size is stretched from another and looks broken.
|
|
220
|
+
- Name things in every size: a figure alone says nothing. Every text is one line. Per line: small about 7 Chinese characters (12 Latin), medium and large about 17 (30). Write it short; never rely on "…".
|
|
221
|
+
- Height is tight. `small` and `medium` fit exactly one of: `[caption; number title; caption]`, 4 lines of text, or a `list` of 3. A `button` counts as 2 lines; with a `source`, one line less (its footer). Anything taller is not drawn.
|
|
222
|
+
- `large` is twice as tall as `medium`: fill it with what medium had no room for (more items, a breakdown, the next ones). Never the medium tree alone, never a figure twice.
|
|
223
|
+
- One big `number` (`style: title`) per size; in `small` at most 4 characters ("81737" is 5: `style: body` there). `unit` is a unit only ("km", "kg", "%"); labels go in a `caption`.
|
|
224
|
+
- Supporting lines: `caption` with `tone: secondary`. Columns side by side get the same lines, so their tops line up. `tone: accent` in one place at most, only for what is urgent. An `icon` only when it tells items apart.
|
|
225
|
+
- A `list` holds things of one kind: write all of them, we show what fits and "N more" — only when the `list` is the whole size, not inside a `column`. Buttons and sentences never go in a `list`; only its first row may have a big number.
|
|
226
|
+
- `action: capture` only when they add items by typing (to-dos, expenses); then no `button` doing the same. A `button` only for one tap that changes something (check in, add a cup, draw again).
|
|
227
|
+
- `badge`: only the number of open items, and then no big number saying the same.
|
|
228
|
+
- Nothing that goes stale: dates ("10/24") and clock times ("last 8:20"), never "today", "tomorrow" or "50 min ago" (not in the `title` either), even if they ask "how long since": a widget has no running clock. A count for one day carries its date.
|
|
229
|
+
- Nothing left: an empty `list`; we draw the empty state.
|
|
230
|
+
- Every text in your partner's language, their items in their own words.
|
|
231
|
+
|
|
232
|
+
### Layouts
|
|
233
|
+
|
|
234
|
+
Pick by what it shows. `[a; b]` is a `column`, `(a | b)` a `row`.
|
|
235
|
+
|
|
236
|
+
1. One figure (spent this month, cups of water, km this week):
|
|
237
|
+
small `[caption label; number title + unit; caption context]` (with a `source`: drop the last caption), or with a tap `[number title + unit; button]`;
|
|
238
|
+
medium `([caption label; number title + unit; caption context] | X)`, X is one thing: a `ring` for a goal, a `button`, or `[caption; number body]`. Never two things stacked beside it;
|
|
239
|
+
large `[the medium tree; list of up to 5 (text grow | number body)]`: a breakdown or the last entries.
|
|
240
|
+
2. Several things, each with a figure or a time (prices, rates, books, a schedule, countdowns): every size is a `list`, one row per thing, the nearest or most important first.
|
|
241
|
+
first row `(text name grow | number title + unit)`, the rest `(text name grow | number body + unit)` or `(text name grow | caption time)`;
|
|
242
|
+
small: the same rows with short names. A `list` grows with taller widgets and counts "N more"; a `column` does not.
|
|
243
|
+
3. Things to tick (to-dos, shopping, habits): see the document below.
|
|
244
|
+
4. Words (a fortune, a word of the day): one `column`, one text per line, never side by side in a `row`.
|
|
245
|
+
small `[text title; caption; caption]`; medium `[text title; text body; caption; caption]`, or `[(text title grow | button); text body; caption]` with a tap; large: up to 4 more `caption` lines. Split long sentences.
|
|
246
|
+
|
|
247
|
+
### Removing one
|
|
248
|
+
|
|
249
|
+
Asked to remove a widget: send `{"v": 1, "id": "todo", "removed": true}` with its `id`. It leaves their widget list; a copy on the home screen shows "deleted" until they long-press it away. `[widget todo] removed` means they deleted it themselves. Either way, stop sending it. A later snapshot with that `id` brings it back, so send one only if they ask again.
|
|
205
250
|
|
|
206
251
|
### Document
|
|
207
252
|
|
|
208
253
|
```json
|
|
209
|
-
{"v": 1, "id": "todo", "title": "To-do", "badge":
|
|
254
|
+
{"v": 1, "id": "todo", "title": "To-do", "badge": 2,
|
|
210
255
|
"action": {"do": "capture"},
|
|
256
|
+
"small": {"type": "list", "items": [
|
|
257
|
+
{"type": "row", "children": [
|
|
258
|
+
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
259
|
+
{"type": "text", "text": "Pay rent", "grow": true}]},
|
|
260
|
+
{"type": "row", "children": [
|
|
261
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
262
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]},
|
|
211
263
|
"medium": {"type": "list", "items": [
|
|
212
264
|
{"type": "row", "children": [
|
|
213
265
|
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
214
266
|
{"type": "text", "text": "Pay rent", "grow": true},
|
|
215
|
-
{"type": "text", "text": "
|
|
267
|
+
{"type": "text", "text": "10/24", "style": "caption", "tone": "accent"}]},
|
|
268
|
+
{"type": "row", "children": [
|
|
269
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
270
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]}}
|
|
216
271
|
```
|
|
217
272
|
|
|
273
|
+
`large`: the same `list` as `medium`. Always list every item; we show what fits and "N more".
|
|
274
|
+
|
|
218
275
|
- `v`: 1. `id`: `^[a-z0-9][a-z0-9_-]{0,31}$`, the same across updates. `title`: up to 40 characters.
|
|
219
276
|
- `badge` (optional): an integer 0–9999, or up to 12 characters.
|
|
220
277
|
- `action` (optional, top right): `capture`, `chat` or `liveware`.
|
|
221
278
|
- `small` / `medium` / `large`: one block tree per size, at least one. A missing size borrows `medium`, then `large`, then `small`. A medium list shows about 3 rows.
|
|
222
279
|
- At most 16 KiB of UTF-8. Unknown fields and block types are skipped.
|
|
223
|
-
-
|
|
280
|
+
- Colours, fonts and spacing are ours; you choose the structure.
|
|
224
281
|
|
|
225
282
|
### Blocks
|
|
226
283
|
|
|
@@ -255,7 +312,7 @@ A widget is a small card on your partner's phone home screen. You write it with
|
|
|
255
312
|
"source": {"url": "https://…", "every": "30m", "fields": {"price": "data.price"}}
|
|
256
313
|
```
|
|
257
314
|
|
|
258
|
-
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h.
|
|
315
|
+
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h. A field is a dot path into that JSON, array items by number (`data.items.0.price`); each step is letters, digits or `_` only, so a key like `xnet-1` cannot be read. Field names: lowercase letters, digits and `_`; every `{name}` you write must be one of them. Write `{price}` in `text` or `number` where the value goes: a value you type in yourself goes stale. Your partner sees the domain.
|
|
259
316
|
|
|
260
317
|
### What comes back
|
|
261
318
|
|
|
@@ -269,6 +326,7 @@ Each tap or entry arrives as an ordinary message with one line for you:
|
|
|
269
326
|
[widget todo] pressed "Refresh" (b1)
|
|
270
327
|
[widget todo] capture: buy milk
|
|
271
328
|
[widget todo] resend
|
|
329
|
+
[widget todo] removed
|
|
272
330
|
```
|
|
273
331
|
|
|
274
332
|
They can arrive late and several at once: apply each in order, they are not duplicates. Then send one new full snapshot. `resend` means a device lost the widget: send the latest snapshot again.
|
package/skills/manifest.json
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"skills": {
|
|
4
4
|
"openclaw": {
|
|
5
5
|
"clawchat-core": {
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.13.0",
|
|
7
7
|
"path": "openclaw/clawchat-core/SKILL.md",
|
|
8
|
-
"sha256": "
|
|
9
|
-
"bytes":
|
|
8
|
+
"sha256": "534096815b7216c180c573cd6917e1b77fb244affaf283fc750a7760d04946d2",
|
|
9
|
+
"bytes": 27385
|
|
10
10
|
},
|
|
11
11
|
"clawchat-liveware": {
|
|
12
12
|
"version": "1.2.4",
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
},
|
|
42
42
|
"hermes": {
|
|
43
43
|
"clawchat-core": {
|
|
44
|
-
"version": "1.
|
|
44
|
+
"version": "1.19.0",
|
|
45
45
|
"path": "hermes/clawchat-core/SKILL.md",
|
|
46
|
-
"sha256": "
|
|
47
|
-
"bytes":
|
|
46
|
+
"sha256": "6da45c16ed831512b11b5ee478961d7deb0a9c23fd8c244cfd181409ba2ac253",
|
|
47
|
+
"bytes": 38138
|
|
48
48
|
},
|
|
49
49
|
"clawchat-liveware": {
|
|
50
50
|
"version": "1.3.1",
|
package/src/runtime.ts
CHANGED
|
@@ -5132,9 +5132,8 @@ async function runOpenclawClawlingGateway(
|
|
|
5132
5132
|
// Synchronous and fire-and-forget on purpose: there is no ack to send (the
|
|
5133
5133
|
// frame is BestEffort and non-ackable).
|
|
5134
5134
|
//
|
|
5135
|
-
// docs/client-integration.md §9.8 describes the permanent tombstone and
|
|
5136
|
-
//
|
|
5137
|
-
// transaction. See that section before changing recall/claim ordering.
|
|
5135
|
+
// docs/client-integration.md §9.8 describes the permanent tombstone and the
|
|
5136
|
+
// BEGIN IMMEDIATE transactions that serialize recall and claim.
|
|
5138
5137
|
client.on("message:recall", (env: Envelope) => {
|
|
5139
5138
|
const target = recallTargetMessageId(env.payload);
|
|
5140
5139
|
if (!target) {
|
package/src/skill-update.ts
CHANGED
|
@@ -70,7 +70,7 @@ export const OFFICIAL_SKILLS_BASE =
|
|
|
70
70
|
* in the install-cli repo, bump this constant, ship it. `liveware-sample.ts`
|
|
71
71
|
* imports the same ref, so the `livewares` tree at that tag is pinned too.
|
|
72
72
|
*/
|
|
73
|
-
export const DEFAULT_SKILLS_REF = "skills-v1.
|
|
73
|
+
export const DEFAULT_SKILLS_REF = "skills-v1.19.0";
|
|
74
74
|
|
|
75
75
|
/** Refuse to treat an absurdly large response as a skill file (defence in depth). */
|
|
76
76
|
export const MAX_SKILL_BYTES = 256 * 1024;
|
package/src/storage.ts
CHANGED
|
@@ -1324,32 +1324,42 @@ export class ClawChatStore {
|
|
|
1324
1324
|
|
|
1325
1325
|
claimMessageOnce(input: MessageInput): true | false | null {
|
|
1326
1326
|
return this.write(() => {
|
|
1327
|
-
// docs/client-integration.md §9.8:
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
.
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1327
|
+
// docs/client-integration.md §9.8: the tombstone check and the claim
|
|
1328
|
+
// insert run in one BEGIN IMMEDIATE transaction.
|
|
1329
|
+
const db = this.requireDb();
|
|
1330
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1331
|
+
try {
|
|
1332
|
+
if (input.messageId && this.isMessageRecalled(input)) {
|
|
1333
|
+
db.exec("COMMIT");
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
const result = db
|
|
1337
|
+
.prepare(
|
|
1338
|
+
`INSERT OR IGNORE INTO clawchat_messages(
|
|
1339
|
+
platform, account_id, kind, direction, event_type, trace_id, chat_id,
|
|
1340
|
+
message_id, text, raw_json, created_at, send_status
|
|
1341
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1342
|
+
)
|
|
1343
|
+
.run(
|
|
1344
|
+
input.platform,
|
|
1345
|
+
input.accountId,
|
|
1346
|
+
input.kind,
|
|
1347
|
+
input.direction,
|
|
1348
|
+
input.eventType,
|
|
1349
|
+
input.traceId ?? null,
|
|
1350
|
+
input.chatId ?? null,
|
|
1351
|
+
input.messageId ?? null,
|
|
1352
|
+
input.text ?? null,
|
|
1353
|
+
toJson(input.raw),
|
|
1354
|
+
input.createdAt ?? Date.now(),
|
|
1355
|
+
input.direction === "outbound" ? "pending" : null,
|
|
1356
|
+
);
|
|
1357
|
+
db.exec("COMMIT");
|
|
1358
|
+
return result.changes > 0;
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
db.exec("ROLLBACK");
|
|
1361
|
+
throw error;
|
|
1362
|
+
}
|
|
1353
1363
|
});
|
|
1354
1364
|
}
|
|
1355
1365
|
|
|
@@ -1506,8 +1516,8 @@ export class ClawChatStore {
|
|
|
1506
1516
|
* account was paired, or one dropped by the inbound filter). `null` means the
|
|
1507
1517
|
* store is unavailable.
|
|
1508
1518
|
*
|
|
1509
|
-
* Records the permanent recall tombstone and
|
|
1510
|
-
* see docs/client-integration.md §9.8
|
|
1519
|
+
* Records the permanent recall tombstone and deletes ledger rows in one
|
|
1520
|
+
* BEGIN IMMEDIATE transaction; see docs/client-integration.md §9.8.
|
|
1511
1521
|
* Returns the number of ledger rows deleted (0 when the recall
|
|
1512
1522
|
* arrived before the message did — the tombstone still stands and the later
|
|
1513
1523
|
* ingest is refused by {@link claimMessageOnce}).
|
|
@@ -1523,17 +1533,24 @@ export class ClawChatStore {
|
|
|
1523
1533
|
if (!input.messageId) return 0;
|
|
1524
1534
|
return this.write(() => {
|
|
1525
1535
|
const db = this.requireDb();
|
|
1526
|
-
db.
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
.
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1536
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1537
|
+
try {
|
|
1538
|
+
db.prepare(
|
|
1539
|
+
`INSERT OR IGNORE INTO recalled_messages(platform, account_id, message_id, recalled_at)
|
|
1540
|
+
VALUES (?, ?, ?, ?)`,
|
|
1541
|
+
).run(input.platform, input.accountId, input.messageId, input.recalledAt ?? Date.now());
|
|
1542
|
+
const result = db
|
|
1543
|
+
.prepare(
|
|
1544
|
+
`DELETE FROM clawchat_messages
|
|
1545
|
+
WHERE platform = ? AND account_id = ? AND message_id = ?`,
|
|
1546
|
+
)
|
|
1547
|
+
.run(input.platform, input.accountId, input.messageId);
|
|
1548
|
+
db.exec("COMMIT");
|
|
1549
|
+
return Number(result.changes);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
db.exec("ROLLBACK");
|
|
1552
|
+
throw error;
|
|
1553
|
+
}
|
|
1537
1554
|
});
|
|
1538
1555
|
}
|
|
1539
1556
|
|
package/src/widget-guide.ts
CHANGED
|
@@ -1,30 +1,87 @@
|
|
|
1
1
|
export const WIDGET_V1_GUIDE = `## Home-screen widgets (widget/v1)
|
|
2
2
|
|
|
3
|
-
A widget is a small card on your partner's phone home screen. You write it with \`clawchat_widget_send\`; their taps come back to you as ordinary messages.
|
|
3
|
+
A widget is a small card on your partner's phone home screen. You write it with \`clawchat_widget_send\`; their taps come back to you as ordinary messages. It only ever goes to your private chat with them.
|
|
4
|
+
|
|
5
|
+
### When to send
|
|
4
6
|
|
|
5
7
|
- Send one only after your partner asked for a widget — a recipe card, or their own words.
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
- If they keep asking you for the same thing (a price, a list, a count), you may offer a widget once. Make it only after they say yes. Never offer the same one twice.
|
|
9
|
+
|
|
10
|
+
### When to update
|
|
11
|
+
|
|
12
|
+
Send a new snapshot in the same turn whenever what it shows has changed:
|
|
13
|
+
|
|
14
|
+
- they tapped or typed on it (a \`[widget <id>] …\` message);
|
|
15
|
+
- they changed the same data in chat: "remind me to buy milk tomorrow" also goes onto the to-do widget;
|
|
16
|
+
- a new day began: "today", "tomorrow" or "N days left" in your last snapshot are now wrong. Send it again in your first turn of the day. If it counts days, schedule yourself a turn early every morning for it.
|
|
17
|
+
|
|
18
|
+
Nothing changed: send nothing. Every message is the full snapshot, never a diff; the latest one per \`id\` wins. At most one per \`id\` a minute. Data that changes by itself (prices, weather) belongs in \`source\`, not in repeated snapshots.
|
|
19
|
+
|
|
20
|
+
### Design
|
|
21
|
+
|
|
22
|
+
- One widget answers one question. \`title\`: a few words. Never repeat it, the \`badge\` or the big number elsewhere; no "tap + to add" hints.
|
|
23
|
+
- Write \`small\`, \`medium\` and \`large\`, each from one layout below, with the same main figure. A missing size is stretched from another and looks broken.
|
|
24
|
+
- Name things in every size: a figure alone says nothing. Every text is one line. Per line: small about 7 Chinese characters (12 Latin), medium and large about 17 (30). Write it short; never rely on "…".
|
|
25
|
+
- Height is tight. \`small\` and \`medium\` fit exactly one of: \`[caption; number title; caption]\`, 4 lines of text, or a \`list\` of 3. A \`button\` counts as 2 lines; with a \`source\`, one line less (its footer). Anything taller is not drawn.
|
|
26
|
+
- \`large\` is twice as tall as \`medium\`: fill it with what medium had no room for (more items, a breakdown, the next ones). Never the medium tree alone, never a figure twice.
|
|
27
|
+
- One big \`number\` (\`style: title\`) per size; in \`small\` at most 4 characters ("81737" is 5: \`style: body\` there). \`unit\` is a unit only ("km", "kg", "%"); labels go in a \`caption\`.
|
|
28
|
+
- Supporting lines: \`caption\` with \`tone: secondary\`. Columns side by side get the same lines, so their tops line up. \`tone: accent\` in one place at most, only for what is urgent. An \`icon\` only when it tells items apart.
|
|
29
|
+
- A \`list\` holds things of one kind: write all of them, we show what fits and "N more" — only when the \`list\` is the whole size, not inside a \`column\`. Buttons and sentences never go in a \`list\`; only its first row may have a big number.
|
|
30
|
+
- \`action: capture\` only when they add items by typing (to-dos, expenses); then no \`button\` doing the same. A \`button\` only for one tap that changes something (check in, add a cup, draw again).
|
|
31
|
+
- \`badge\`: only the number of open items, and then no big number saying the same.
|
|
32
|
+
- Nothing that goes stale: dates ("10/24") and clock times ("last 8:20"), never "today", "tomorrow" or "50 min ago" (not in the \`title\` either), even if they ask "how long since": a widget has no running clock. A count for one day carries its date.
|
|
33
|
+
- Nothing left: an empty \`list\`; we draw the empty state.
|
|
34
|
+
- Every text in your partner's language, their items in their own words.
|
|
35
|
+
|
|
36
|
+
### Layouts
|
|
37
|
+
|
|
38
|
+
Pick by what it shows. \`[a; b]\` is a \`column\`, \`(a | b)\` a \`row\`.
|
|
39
|
+
|
|
40
|
+
1. One figure (spent this month, cups of water, km this week):
|
|
41
|
+
small \`[caption label; number title + unit; caption context]\` (with a \`source\`: drop the last caption), or with a tap \`[number title + unit; button]\`;
|
|
42
|
+
medium \`([caption label; number title + unit; caption context] | X)\`, X is one thing: a \`ring\` for a goal, a \`button\`, or \`[caption; number body]\`. Never two things stacked beside it;
|
|
43
|
+
large \`[the medium tree; list of up to 5 (text grow | number body)]\`: a breakdown or the last entries.
|
|
44
|
+
2. Several things, each with a figure or a time (prices, rates, books, a schedule, countdowns): every size is a \`list\`, one row per thing, the nearest or most important first.
|
|
45
|
+
first row \`(text name grow | number title + unit)\`, the rest \`(text name grow | number body + unit)\` or \`(text name grow | caption time)\`;
|
|
46
|
+
small: the same rows with short names. A \`list\` grows with taller widgets and counts "N more"; a \`column\` does not.
|
|
47
|
+
3. Things to tick (to-dos, shopping, habits): see the document below.
|
|
48
|
+
4. Words (a fortune, a word of the day): one \`column\`, one text per line, never side by side in a \`row\`.
|
|
49
|
+
small \`[text title; caption; caption]\`; medium \`[text title; text body; caption; caption]\`, or \`[(text title grow | button); text body; caption]\` with a tap; large: up to 4 more \`caption\` lines. Split long sentences.
|
|
50
|
+
|
|
51
|
+
### Removing one
|
|
52
|
+
|
|
53
|
+
Asked to remove a widget: send \`{"v": 1, "id": "todo", "removed": true}\` with its \`id\`. It leaves their widget list; a copy on the home screen shows "deleted" until they long-press it away. \`[widget todo] removed\` means they deleted it themselves. Either way, stop sending it. A later snapshot with that \`id\` brings it back, so send one only if they ask again.
|
|
9
54
|
|
|
10
55
|
### Document
|
|
11
56
|
|
|
12
57
|
\`\`\`json
|
|
13
|
-
{"v": 1, "id": "todo", "title": "To-do", "badge":
|
|
58
|
+
{"v": 1, "id": "todo", "title": "To-do", "badge": 2,
|
|
14
59
|
"action": {"do": "capture"},
|
|
60
|
+
"small": {"type": "list", "items": [
|
|
61
|
+
{"type": "row", "children": [
|
|
62
|
+
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
63
|
+
{"type": "text", "text": "Pay rent", "grow": true}]},
|
|
64
|
+
{"type": "row", "children": [
|
|
65
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
66
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]},
|
|
15
67
|
"medium": {"type": "list", "items": [
|
|
16
68
|
{"type": "row", "children": [
|
|
17
69
|
{"type": "check", "id": "t1", "label": "Pay rent", "checked": false},
|
|
18
70
|
{"type": "text", "text": "Pay rent", "grow": true},
|
|
19
|
-
{"type": "text", "text": "
|
|
71
|
+
{"type": "text", "text": "10/24", "style": "caption", "tone": "accent"}]},
|
|
72
|
+
{"type": "row", "children": [
|
|
73
|
+
{"type": "check", "id": "t2", "label": "Call mom", "checked": false},
|
|
74
|
+
{"type": "text", "text": "Call mom", "grow": true}]}]}}
|
|
20
75
|
\`\`\`
|
|
21
76
|
|
|
77
|
+
\`large\`: the same \`list\` as \`medium\`. Always list every item; we show what fits and "N more".
|
|
78
|
+
|
|
22
79
|
- \`v\`: 1. \`id\`: \`^[a-z0-9][a-z0-9_-]{0,31}$\`, the same across updates. \`title\`: up to 40 characters.
|
|
23
80
|
- \`badge\` (optional): an integer 0–9999, or up to 12 characters.
|
|
24
81
|
- \`action\` (optional, top right): \`capture\`, \`chat\` or \`liveware\`.
|
|
25
82
|
- \`small\` / \`medium\` / \`large\`: one block tree per size, at least one. A missing size borrows \`medium\`, then \`large\`, then \`small\`. A medium list shows about 3 rows.
|
|
26
83
|
- At most 16 KiB of UTF-8. Unknown fields and block types are skipped.
|
|
27
|
-
-
|
|
84
|
+
- Colours, fonts and spacing are ours; you choose the structure.
|
|
28
85
|
|
|
29
86
|
### Blocks
|
|
30
87
|
|
|
@@ -59,7 +116,7 @@ A widget is a small card on your partner's phone home screen. You write it with
|
|
|
59
116
|
"source": {"url": "https://…", "every": "30m", "fields": {"price": "data.price"}}
|
|
60
117
|
\`\`\`
|
|
61
118
|
|
|
62
|
-
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h.
|
|
119
|
+
The phone fetches it itself: HTTPS GET, no credentials, JSON up to 64 KiB, every 15m to 24h. A field is a dot path into that JSON, array items by number (\`data.items.0.price\`); each step is letters, digits or \`_\` only, so a key like \`xnet-1\` cannot be read. Field names: lowercase letters, digits and \`_\`; every \`{name}\` you write must be one of them. Write \`{price}\` in \`text\` or \`number\` where the value goes: a value you type in yourself goes stale. Your partner sees the domain.
|
|
63
120
|
|
|
64
121
|
### What comes back
|
|
65
122
|
|
|
@@ -73,6 +130,7 @@ Each tap or entry arrives as an ordinary message with one line for you:
|
|
|
73
130
|
[widget todo] pressed "Refresh" (b1)
|
|
74
131
|
[widget todo] capture: buy milk
|
|
75
132
|
[widget todo] resend
|
|
133
|
+
[widget todo] removed
|
|
76
134
|
\`\`\`
|
|
77
135
|
|
|
78
136
|
They can arrive late and several at once: apply each in order, they are not duplicates. Then send one new full snapshot. \`resend\` means a device lost the widget: send the latest snapshot again.
|