openclaw-quiubo 2.6.36 → 2.6.37
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/POSTS.md +122 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +115 -4
- package/dist/index.js.map +4 -4
- package/dist/src/create-post-tool.d.ts +49 -0
- package/dist/src/create-post-tool.d.ts.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
package/POSTS.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Agent Posts
|
|
2
|
+
|
|
3
|
+
Let agents create posts in Quiubo groups — image + optional caption, visible in the group's post feed, auto-expires after 24 hours.
|
|
4
|
+
|
|
5
|
+
## Why This Exists
|
|
6
|
+
|
|
7
|
+
Without this tool, an agent asked to "create a post" will generate an image and send it as a chat message via `sendMedia`. The agent has no way to distinguish "send a message with an image" from "create a post" — both route through the same `deliver-callback` pipeline.
|
|
8
|
+
|
|
9
|
+
The `quiubo_create_post` tool gives agents an explicit action for posts. The agent calls it by name, the image goes through the presigned upload flow, and the result appears in the post feed — not the chat.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- A configured Quiubo account with `apiKey` and `botIdentityId` in `channels.quiubo.accounts`
|
|
14
|
+
- The agent must have an image file on disk (`.jpg`, `.jpeg`, `.png`, or `.webp`, max 5MB)
|
|
15
|
+
|
|
16
|
+
If the Quiubo channel isn't configured, the tool silently doesn't register — agents won't see it.
|
|
17
|
+
|
|
18
|
+
## How It Works
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Agent decides to create a post
|
|
22
|
+
│
|
|
23
|
+
▼
|
|
24
|
+
quiubo_create_post(image_path, caption?, group_id?)
|
|
25
|
+
│
|
|
26
|
+
├─ Reads image from disk
|
|
27
|
+
├─ Validates format + size
|
|
28
|
+
├─ POST /v1/sdk/posts/presign → gets upload URL + post record
|
|
29
|
+
├─ PUT to presigned S3 URL → uploads image bytes
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
Post appears in group feed (not chat)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The tool creates its own API client from the account config — no shared state with the channel gateway.
|
|
36
|
+
|
|
37
|
+
## Tool Parameters
|
|
38
|
+
|
|
39
|
+
| Parameter | Type | Required | Description |
|
|
40
|
+
|-----------|------|----------|-------------|
|
|
41
|
+
| `image_path` | string | Yes | Absolute path to image file on disk |
|
|
42
|
+
| `caption` | string | No | Text caption (max 500 chars) |
|
|
43
|
+
| `group_id` | string | No | Target group UUID. Defaults to the current conversation group. |
|
|
44
|
+
|
|
45
|
+
## Tool Response
|
|
46
|
+
|
|
47
|
+
**Success:**
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"ok": true,
|
|
51
|
+
"postId": "post-uuid",
|
|
52
|
+
"imageUrl": "https://...",
|
|
53
|
+
"groupId": "group-uuid"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Error:**
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"ok": false,
|
|
61
|
+
"error": "Unsupported image format: .gif. Use .jpg, .png, or .webp"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Teaching Your Agent
|
|
66
|
+
|
|
67
|
+
Add something like this to the agent's identity/instructions file (`SOUL.md`, `IDENTITY.md`, or system prompt):
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## Posts
|
|
71
|
+
|
|
72
|
+
You have a `quiubo_create_post` tool. Use it when asked to create a post, share a photo to the feed, or post an update.
|
|
73
|
+
|
|
74
|
+
Posts are different from messages:
|
|
75
|
+
- Posts appear in the group's **post feed**, not the chat
|
|
76
|
+
- Posts require an **image** (messages don't)
|
|
77
|
+
- Posts auto-expire after **24 hours**
|
|
78
|
+
|
|
79
|
+
Workflow:
|
|
80
|
+
1. Generate or locate the image file
|
|
81
|
+
2. Call `quiubo_create_post` with `image_path` and optional `caption`
|
|
82
|
+
3. The post appears in the group feed automatically
|
|
83
|
+
|
|
84
|
+
Do NOT use sendMessage/sendMedia for posts — that sends a chat message, not a post.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Group Targeting
|
|
88
|
+
|
|
89
|
+
The tool resolves the target group in this order:
|
|
90
|
+
|
|
91
|
+
1. **`group_id` parameter** — if the agent passes it explicitly
|
|
92
|
+
2. **Session key** — extracted from the OpenClaw session key (`agent:<name>:quiubo:<uuid>`)
|
|
93
|
+
|
|
94
|
+
For most conversational use ("create a post in this group"), the session key auto-resolves. The `group_id` parameter is for cross-posting to a different group.
|
|
95
|
+
|
|
96
|
+
## Example Agent Interaction
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
User: Create a post with today's weather forecast
|
|
100
|
+
|
|
101
|
+
Agent: (generates weather image → saves to /tmp/weather-2026-02-28.png)
|
|
102
|
+
Agent: (calls quiubo_create_post)
|
|
103
|
+
image_path: /tmp/weather-2026-02-28.png
|
|
104
|
+
caption: "☀️ Today's forecast: Clear skies, 22°C"
|
|
105
|
+
|
|
106
|
+
Tool result: { ok: true, postId: "abc-123", groupId: "def-456" }
|
|
107
|
+
|
|
108
|
+
Agent: Done — the weather forecast is now in the group's post feed.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Troubleshooting
|
|
112
|
+
|
|
113
|
+
| Symptom | Fix |
|
|
114
|
+
|---------|-----|
|
|
115
|
+
| Agent sends image as chat message instead of post | Agent doesn't know about the tool. Add instructions to its identity file (see [Teaching Your Agent](#teaching-your-agent)). |
|
|
116
|
+
| Tool doesn't appear for the agent | Check that `channels.quiubo.accounts.default` has both `apiKey` and `botIdentityId`. The factory returns null without them. |
|
|
117
|
+
| "No group_id provided and could not resolve from session context" | The session key doesn't contain a group UUID. Pass `group_id` explicitly. |
|
|
118
|
+
| "No botIdentityId configured" | Add `botIdentityId` to the account config in `channels.quiubo.accounts`. |
|
|
119
|
+
| "Unsupported image format" | Only `.jpg`, `.jpeg`, `.png`, `.webp` are supported. Convert the image first. |
|
|
120
|
+
| "Image exceeds 5MB limit" | Compress or resize the image before calling the tool. |
|
|
121
|
+
| "Cannot read image" | The `image_path` doesn't exist or isn't readable. Check the path is absolute and the file was saved. |
|
|
122
|
+
| Post created but not visible | Check the group UUID is correct. Posts are scoped to a group and expire after 24 hours. |
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACvK,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,gBAAgB,CAAC;AAGxB,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IAEjB,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,GAAG,CAAA;KAAE,KAAK,IAAI,CAAC;IAEjD,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7D;AAED,QAAA,MAAM,MAAM;;;;kBAII,iBAAiB;CAKhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14343,11 +14343,11 @@ function resolveOutboundGroupId(ctx) {
|
|
|
14343
14343
|
}
|
|
14344
14344
|
async function resolveAnnounceGroupId(accountId, log) {
|
|
14345
14345
|
try {
|
|
14346
|
-
const { readFile:
|
|
14346
|
+
const { readFile: readFile4 } = await import("node:fs/promises");
|
|
14347
14347
|
const { join: join3 } = await import("node:path");
|
|
14348
14348
|
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
14349
14349
|
const cronPath = join3(homeDir, ".openclaw", "cron", "jobs.json");
|
|
14350
|
-
const raw = await
|
|
14350
|
+
const raw = await readFile4(cronPath, "utf-8");
|
|
14351
14351
|
const parsed = JSON.parse(raw);
|
|
14352
14352
|
const jobs = parsed?.jobs ?? [];
|
|
14353
14353
|
for (const job of jobs) {
|
|
@@ -14367,12 +14367,12 @@ async function resolveAnnounceGroupId(accountId, log) {
|
|
|
14367
14367
|
}
|
|
14368
14368
|
async function getActivityData(runtime2, log, agentId) {
|
|
14369
14369
|
try {
|
|
14370
|
-
const { readFile:
|
|
14370
|
+
const { readFile: readFile4 } = await import("node:fs/promises");
|
|
14371
14371
|
const { join: join3 } = await import("node:path");
|
|
14372
14372
|
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
14373
14373
|
const cronPath = join3(homeDir, ".openclaw", "cron", "jobs.json");
|
|
14374
14374
|
log?.info?.(`getActivityData: reading ${cronPath} (agentId=${agentId})`);
|
|
14375
|
-
const raw = await
|
|
14375
|
+
const raw = await readFile4(cronPath, "utf-8");
|
|
14376
14376
|
const parsed = JSON.parse(raw);
|
|
14377
14377
|
const jobs = parsed?.jobs ?? [];
|
|
14378
14378
|
const items = jobs.filter((j) => j.enabled !== false && (!agentId || j.agentId === agentId)).map((job) => ({
|
|
@@ -14623,6 +14623,116 @@ async function routeInboundMessage(opts) {
|
|
|
14623
14623
|
log?.info?.(`[${accountId}] Quiubo: message processed from ${senderId}`);
|
|
14624
14624
|
}
|
|
14625
14625
|
|
|
14626
|
+
// src/create-post-tool.ts
|
|
14627
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
14628
|
+
var IMAGE_MIME_TYPES2 = {
|
|
14629
|
+
".jpg": "image/jpeg",
|
|
14630
|
+
".jpeg": "image/jpeg",
|
|
14631
|
+
".png": "image/png",
|
|
14632
|
+
".webp": "image/webp"
|
|
14633
|
+
};
|
|
14634
|
+
var MAX_IMAGE_BYTES2 = 5 * 1024 * 1024;
|
|
14635
|
+
var DEFAULT_API_URL2 = "https://api.quiubo.io";
|
|
14636
|
+
function extractGroupIdFromSessionKey(key) {
|
|
14637
|
+
if (!key) return void 0;
|
|
14638
|
+
const match = key.match(/quiubo:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i);
|
|
14639
|
+
return match?.[1];
|
|
14640
|
+
}
|
|
14641
|
+
function jsonResult(payload) {
|
|
14642
|
+
return {
|
|
14643
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
14644
|
+
details: payload
|
|
14645
|
+
};
|
|
14646
|
+
}
|
|
14647
|
+
function errorResult(msg) {
|
|
14648
|
+
return {
|
|
14649
|
+
content: [{ type: "text", text: msg }],
|
|
14650
|
+
details: { ok: false, error: msg }
|
|
14651
|
+
};
|
|
14652
|
+
}
|
|
14653
|
+
function createQuiuboPostToolFactory(ctx) {
|
|
14654
|
+
const quiuboConfig = ctx.config?.channels?.quiubo;
|
|
14655
|
+
const accountCfg = quiuboConfig?.accounts?.default;
|
|
14656
|
+
if (!accountCfg?.apiKey) return null;
|
|
14657
|
+
const client = new QuiuboApiClient(
|
|
14658
|
+
accountCfg.apiUrl ?? DEFAULT_API_URL2,
|
|
14659
|
+
accountCfg.apiKey
|
|
14660
|
+
);
|
|
14661
|
+
const botIdentityId = accountCfg.botIdentityId;
|
|
14662
|
+
const defaultGroupId = extractGroupIdFromSessionKey(ctx.sessionKey);
|
|
14663
|
+
return {
|
|
14664
|
+
name: "quiubo_create_post",
|
|
14665
|
+
label: "Create Quiubo Post",
|
|
14666
|
+
description: "Create a post with an image in a Quiubo group. Posts appear in the group post feed and auto-expire after 24 hours. REQUIRES an image file path on disk (.jpg/.jpeg/.png/.webp, max 5MB). Optionally include a caption (max 500 chars).",
|
|
14667
|
+
parameters: {
|
|
14668
|
+
type: "object",
|
|
14669
|
+
properties: {
|
|
14670
|
+
image_path: {
|
|
14671
|
+
type: "string",
|
|
14672
|
+
description: "Absolute path to image file on disk"
|
|
14673
|
+
},
|
|
14674
|
+
caption: {
|
|
14675
|
+
type: "string",
|
|
14676
|
+
description: "Optional text caption (max 500 chars)"
|
|
14677
|
+
},
|
|
14678
|
+
group_id: {
|
|
14679
|
+
type: "string",
|
|
14680
|
+
description: "Target group UUID. Omit to use current conversation group."
|
|
14681
|
+
}
|
|
14682
|
+
},
|
|
14683
|
+
required: ["image_path"]
|
|
14684
|
+
},
|
|
14685
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14686
|
+
execute: async (_toolCallId, params) => {
|
|
14687
|
+
const groupId = params.group_id || defaultGroupId;
|
|
14688
|
+
if (!groupId) {
|
|
14689
|
+
return errorResult("No group_id provided and could not resolve from session context");
|
|
14690
|
+
}
|
|
14691
|
+
if (!botIdentityId) {
|
|
14692
|
+
return errorResult("No botIdentityId configured");
|
|
14693
|
+
}
|
|
14694
|
+
const imagePath = params.image_path;
|
|
14695
|
+
const ext = imagePath.substring(imagePath.lastIndexOf(".")).toLowerCase();
|
|
14696
|
+
if (!(ext in IMAGE_MIME_TYPES2)) {
|
|
14697
|
+
return errorResult(`Unsupported image format: ${ext}. Use .jpg, .png, or .webp`);
|
|
14698
|
+
}
|
|
14699
|
+
let imageBuffer;
|
|
14700
|
+
try {
|
|
14701
|
+
imageBuffer = await readFile3(imagePath);
|
|
14702
|
+
} catch {
|
|
14703
|
+
return errorResult(`Cannot read image: ${imagePath}`);
|
|
14704
|
+
}
|
|
14705
|
+
if (imageBuffer.length > MAX_IMAGE_BYTES2) {
|
|
14706
|
+
return errorResult(
|
|
14707
|
+
`Image exceeds 5MB limit (${(imageBuffer.length / 1024 / 1024).toFixed(1)}MB)`
|
|
14708
|
+
);
|
|
14709
|
+
}
|
|
14710
|
+
const contentType = IMAGE_MIME_TYPES2[ext];
|
|
14711
|
+
try {
|
|
14712
|
+
const presign = await client.createPost({
|
|
14713
|
+
identityId: botIdentityId,
|
|
14714
|
+
contentType,
|
|
14715
|
+
content: params.caption || void 0,
|
|
14716
|
+
groupId
|
|
14717
|
+
});
|
|
14718
|
+
await client.uploadToPresignedUrl(presign.uploadUrl, imageBuffer, contentType);
|
|
14719
|
+
return jsonResult({
|
|
14720
|
+
ok: true,
|
|
14721
|
+
postId: presign.post.id,
|
|
14722
|
+
imageUrl: presign.imageUrl,
|
|
14723
|
+
groupId
|
|
14724
|
+
});
|
|
14725
|
+
} catch (error) {
|
|
14726
|
+
if (error instanceof QuiuboApiError) {
|
|
14727
|
+
return errorResult(`Quiubo API error: ${error.status} \u2014 ${error.body}`);
|
|
14728
|
+
}
|
|
14729
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
14730
|
+
return errorResult(`Post creation failed: ${msg}`);
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14733
|
+
};
|
|
14734
|
+
}
|
|
14735
|
+
|
|
14626
14736
|
// src/polling-gateway.ts
|
|
14627
14737
|
var PollingGateway = class {
|
|
14628
14738
|
client;
|
|
@@ -14770,6 +14880,7 @@ var plugin = {
|
|
|
14770
14880
|
register(api) {
|
|
14771
14881
|
setQuiuboRuntime(api.runtime);
|
|
14772
14882
|
api.registerChannel({ plugin: quiuboPlugin });
|
|
14883
|
+
api.registerTool(createQuiuboPostToolFactory, { name: "quiubo_create_post" });
|
|
14773
14884
|
}
|
|
14774
14885
|
};
|
|
14775
14886
|
var index_default = plugin;
|