openclaw-quiubo 2.6.42 → 2.6.44

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/MULTI_AGENT.md CHANGED
@@ -201,7 +201,15 @@ Cron jobs are scoped per agent via the `agentId` field:
201
201
  - **`delivery.channel`**: Must be `"quiubo"`.
202
202
  - **`delivery.to`**: The raw group UUID (e.g. `eec8a4ce-d08d-483a-ba5d-90a43ce8f521`). Find it in the agent's welcome chat or via the API.
203
203
 
204
- > **Multi-agent caveat:** The fallback resolver (`resolveAnnounceGroupId`) reads cron jobs from disk but does not filter by accountId. If multiple agents have cron jobs, it may resolve to the wrong agent's target group. Keep one cron job per agent, or ensure each agent's delivery target is unambiguous.
204
+ > **Multi-agent caveat:** The announce delivery path does not reliably resolve targets for named agents ([openclaw/openclaw#32432](https://github.com/openclaw/openclaw/issues/32432)). Use `--no-deliver` so the agent sends messages itself:
205
+ >
206
+ > ```bash
207
+ > openclaw cron add --name "standup" --agent project-manager --every 24h \
208
+ > --no-deliver --to <group-uuid> \
209
+ > --message 'Post the daily standup to the group chat.'
210
+ > ```
211
+ >
212
+ > With `--no-deliver`, the agent uses its own channel binding to send messages, which correctly resolves identity and routing. See the [README cron section](./README.md#named-agent-crons) for details.
205
213
 
206
214
  ## Full Config Reference
207
215
 
package/README.md CHANGED
@@ -34,6 +34,45 @@ The interactive wizard will:
34
34
  | SDK API Key | Quiubo app > Settings > Developer > API Keys |
35
35
  | Bot Identity | Created during setup wizard, or pre-create in Developer console |
36
36
 
37
+ ## Quick Start: Talking to your agent
38
+
39
+ Once setup completes, your agent is ready to chat:
40
+
41
+ 1. **Open Quiubo** — your agent appears as a contact with its own chat
42
+ 2. **Send a message** — the agent receives it and responds automatically
43
+ 3. **Add to groups** — invite the agent to any group, then @mention it to get its attention
44
+
45
+ ### How agents receive messages
46
+
47
+ | Context | How to trigger the agent |
48
+ |---------|------------------------|
49
+ | 1:1 chat (agent channel) | Just send a message — the agent always responds |
50
+ | Group (agent is a member) | @mention the agent: `@BotName do something` |
51
+ | Group (agent-only) | Every message triggers all agents (auto-broadcast) |
52
+ | Group (directory agent) | @mention required — agents with `triggerMode: "mention"` only respond when mentioned |
53
+
54
+ ### @team shortcut
55
+
56
+ In groups with 2+ agents, type `@Team` to mention all agents at once. The @Team mention expands to individual agent mentions before delivery.
57
+
58
+ ### Scheduled messages
59
+
60
+ Agents can send messages on a schedule using cron jobs:
61
+
62
+ ```bash
63
+ # Default agent — announce mode works
64
+ openclaw cron add --name "daily-report" --every 24h \
65
+ --channel quiubo --to <group-uuid> --announce \
66
+ --message "Generate and post the daily report."
67
+
68
+ # Named agent — use --no-deliver (agent sends the message itself)
69
+ openclaw cron add --name "standup" --agent my-agent --every 24h \
70
+ --no-deliver --to <group-uuid> \
71
+ --message "Post the daily standup to the group chat."
72
+ ```
73
+
74
+ See [Cron delivery](#scheduled-messages--cron-delivery) for details on delivery modes.
75
+
37
76
  ## Configuration
38
77
 
39
78
  ```yaml
@@ -204,13 +243,36 @@ A typing indicator is sent immediately when processing begins, then repeated eve
204
243
  Cron jobs that deliver to Quiubo groups **must** include `--to <groupId>`:
205
244
 
206
245
  ```bash
207
- openclaw cron add --every 30m --channel quiubo --to 7868cc21-... "Check in with the team"
246
+ openclaw cron add --name "team-checkin" --every 30m --channel quiubo --to 7868cc21-... "Check in with the team"
208
247
  ```
209
248
 
210
249
  Without `--to`, the extension cannot determine which group to deliver to. Unlike 1:1 sessions (which have a single bound chat), group sessions are multi-tenant — the target must be explicit.
211
250
 
212
251
  > **Note:** The `--to` value is the Quiubo group UUID, visible in the group detail screen or conversation URL.
213
252
 
253
+ #### Named agent crons
254
+
255
+ The `--announce` delivery mode does not work reliably with `--agent <id>` (see [openclaw/openclaw#32432](https://github.com/openclaw/openclaw/issues/32432)). The workaround is `--no-deliver` — the agent sends the message itself using the message tool:
256
+
257
+ ```bash
258
+ openclaw cron add --name "giskard-standup" --agent giskard --every 24h \
259
+ --no-deliver \
260
+ --to 7868cc21-... \
261
+ --message 'Post the daily standup summary to the Quiubo group chat.'
262
+ ```
263
+
264
+ With `--no-deliver`, the cron skips the announce pipeline entirely. The agent receives the prompt and sends messages through its own account binding, which correctly resolves identity and group routing.
265
+
266
+ **Why this works:** Announce delivery resolves targets using a different code path that lacks proper account resolution for named agents. With `--no-deliver`, the agent uses the standard message tool path, which goes through the agent's own channel binding (`quiubo:accountId`).
267
+
268
+ #### Delivery mode comparison
269
+
270
+ | Mode | Flag | How it works | Named agent support |
271
+ |------|------|-------------|-------------------|
272
+ | Announce | `--announce` (default) | Cron delivers output via channel adapter | ❌ Fails for `--agent` |
273
+ | No deliver | `--no-deliver` | Agent sends messages itself via message tool | ✅ Works |
274
+ | Webhook | `--webhook --to <url>` | Cron POSTs to your endpoint | ✅ Works |
275
+
214
276
  See [#84](https://github.com/bitlabs-com/quiubo/issues/84) for a planned improvement to auto-resolve the target from the session bound group.
215
277
 
216
278
  ## Managing Accounts
package/dist/index.d.ts CHANGED
@@ -12,12 +12,17 @@ export { startWebhookServer, type WebhookHandlerOptions } from './src/webhook-ha
12
12
  export type { QuiuboAccountConfig, QuiuboGroup, QuiuboMessage, QuiuboIdentity, QuiuboJoinToken, QuiuboAuthResponse, WebhookPayload, WebhookMessageData, WebhookJoinTokenRedeemedData, } from './src/types.js';
13
13
  interface OpenClawPluginApi {
14
14
  runtime: unknown;
15
+ config?: unknown;
15
16
  registerChannel: (opts: {
16
17
  plugin: any;
17
18
  }) => void;
18
19
  registerTool: (tool: any, opts?: {
19
20
  name?: string;
20
21
  }) => void;
22
+ registerHook: (events: string | string[], handler: (...args: any[]) => any, opts?: {
23
+ name?: string;
24
+ description?: string;
25
+ }) => void;
21
26
  }
22
27
  declare const plugin: {
23
28
  id: string;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,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;IACjB,MAAM,CAAC,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;IAE5D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACrI;AAED,QAAA,MAAM,MAAM;;;;kBAII,iBAAiB;CAMhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -14818,6 +14818,123 @@ function createQuiuboPostToolFactory(ctx) {
14818
14818
  };
14819
14819
  }
14820
14820
 
14821
+ // src/group-context-hook.ts
14822
+ var CACHE_TTL_MS = 6e4;
14823
+ var cache = /* @__PURE__ */ new Map();
14824
+ function getCached(groupId) {
14825
+ const entry = cache.get(groupId);
14826
+ if (!entry) return void 0;
14827
+ if (Date.now() > entry.expiry) {
14828
+ cache.delete(groupId);
14829
+ return void 0;
14830
+ }
14831
+ return entry.data;
14832
+ }
14833
+ function setCache(groupId, data) {
14834
+ cache.set(groupId, { data, expiry: Date.now() + CACHE_TTL_MS });
14835
+ }
14836
+ function extractGroupIdFromSessionKey2(key) {
14837
+ if (!key) return void 0;
14838
+ const match = key.match(
14839
+ /quiubo:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i
14840
+ );
14841
+ return match?.[1];
14842
+ }
14843
+ function extractAgentIdFromSessionKey2(key) {
14844
+ if (!key) return "main";
14845
+ const match = key.match(/^agent:([^:]+):quiubo:/);
14846
+ return match?.[1] ?? "main";
14847
+ }
14848
+ function resolveAccountId2(config, agentId) {
14849
+ const bindings = config?.bindings ?? [];
14850
+ const matched = bindings.find(
14851
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14852
+ (b) => b?.match?.channel === "quiubo" && b?.agentId === agentId
14853
+ );
14854
+ return matched?.match?.accountId ?? "default";
14855
+ }
14856
+ var DEFAULT_API_URL3 = "https://api.quiubo.chat/v1";
14857
+ async function fetchGroupContext(config, sessionKey) {
14858
+ const groupId = extractGroupIdFromSessionKey2(sessionKey);
14859
+ if (!groupId) return void 0;
14860
+ const cached = getCached(groupId);
14861
+ if (cached) return cached;
14862
+ const agentId = extractAgentIdFromSessionKey2(sessionKey);
14863
+ const accountId = resolveAccountId2(config, agentId);
14864
+ const quiuboConfig = config?.channels?.quiubo;
14865
+ const accountCfg = quiuboConfig?.accounts?.[accountId];
14866
+ if (!accountCfg?.apiKey) return void 0;
14867
+ const client = new QuiuboApiClient(
14868
+ accountCfg.apiUrl ?? DEFAULT_API_URL3,
14869
+ accountCfg.apiKey
14870
+ );
14871
+ const [group, { members }, { agents }] = await Promise.all([
14872
+ client.getGroup(groupId),
14873
+ client.listGroupMembers(groupId),
14874
+ client.listAgents()
14875
+ ]);
14876
+ const botIdentityIds = new Set(agents.map((a) => a.identityId));
14877
+ const currentAgent = agents.find((a) => a.identityId === accountCfg.botIdentityId);
14878
+ const e2eeEnabled = currentAgent?.e2eeConfigured ?? false;
14879
+ let scopes = [];
14880
+ if (currentAgent) {
14881
+ try {
14882
+ const status = await client.getGroupAgent(currentAgent.id, groupId);
14883
+ scopes = status?.grantedScopes ?? [];
14884
+ } catch {
14885
+ }
14886
+ }
14887
+ const data = {
14888
+ groupId,
14889
+ groupName: group.name,
14890
+ groupType: group.groupType ?? "standard",
14891
+ e2ee: e2eeEnabled,
14892
+ members: members.map((m) => ({
14893
+ displayName: m.displayName ?? m.identityId,
14894
+ isBot: botIdentityIds.has(m.identityId),
14895
+ role: m.role
14896
+ })),
14897
+ scopes
14898
+ };
14899
+ setCache(groupId, data);
14900
+ return data;
14901
+ }
14902
+ function formatContextBlock(data) {
14903
+ const memberList = data.members.map((m) => `${m.displayName} (${m.isBot ? "bot" : "human"}${m.role === "owner" ? ", owner" : ""})`).join(", ");
14904
+ const lines = [
14905
+ "[Quiubo Group Context]",
14906
+ `Group: "${data.groupName}" (${data.groupId})`,
14907
+ `Type: ${data.groupType === "agent_channel" ? "agent channel" : data.groupType}`,
14908
+ `Members: ${memberList}`,
14909
+ `E2EE: ${data.e2ee ? "enabled" : "disabled"}`
14910
+ ];
14911
+ if (data.scopes.length > 0) {
14912
+ lines.push(`Scopes: ${data.scopes.join(", ")}`);
14913
+ }
14914
+ return lines.join("\n");
14915
+ }
14916
+ function registerGroupContextHook(api) {
14917
+ api.registerHook(
14918
+ "before_prompt_build",
14919
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14920
+ async (_event, ctx) => {
14921
+ if (ctx.channelId !== "quiubo") return;
14922
+ try {
14923
+ const config = api.runtime?.config ?? api.config;
14924
+ const data = await fetchGroupContext(config, ctx.sessionKey);
14925
+ if (!data) return;
14926
+ return {
14927
+ prependSystemContext: formatContextBlock(data)
14928
+ };
14929
+ } catch (err) {
14930
+ console.warn(`[quiubo] group-context-hook failed: ${err}`);
14931
+ return;
14932
+ }
14933
+ },
14934
+ { name: "quiubo-group-context" }
14935
+ );
14936
+ }
14937
+
14821
14938
  // src/polling-gateway.ts
14822
14939
  var PollingGateway = class {
14823
14940
  client;
@@ -14966,6 +15083,7 @@ var plugin = {
14966
15083
  setQuiuboRuntime(api.runtime);
14967
15084
  api.registerChannel({ plugin: quiuboPlugin });
14968
15085
  api.registerTool(createQuiuboPostToolFactory, { name: "quiubo_create_post" });
15086
+ registerGroupContextHook(api);
14969
15087
  }
14970
15088
  };
14971
15089
  var index_default = plugin;