@clawling/clawchat-plugin-openclaw 2026.9.22-2 → 2026.9.23-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.
@@ -1,5 +1,10 @@
1
1
  import { ClawlingApiError, } from "./api-types.js";
2
2
  import { CHANNEL_ID } from "./config.js";
3
+ const ORCH = "/v1/agents/me/orchestration";
4
+ /** Body + content-type for an orchestration write, so the two never drift apart. */
5
+ function orchJson(payload) {
6
+ return { body: JSON.stringify(payload), headers: { "content-type": "application/json" } };
7
+ }
3
8
  export function buildPluginReportBody(input) {
4
9
  const body = {
5
10
  device_id: input.deviceId,
@@ -264,8 +269,13 @@ export function createOpenclawClawlingApiClient(opts) {
264
269
  }
265
270
  throw new ClawlingApiError("transport", text ? "invalid envelope: missing numeric `code`" : "non-JSON response: empty body", { status: res.status, path });
266
271
  }
267
- async function call(method, path, init) {
268
- let res;
272
+ /**
273
+ * Shared by `call<T>` and `orchCall`: build the request, invoke `fetchImpl`,
274
+ * and normalize any network-level failure into a `ClawlingApiError`. Kept
275
+ * as one function so the two callers' request-building can never drift
276
+ * apart again the way `call<T>` and `orchCall` briefly did.
277
+ */
278
+ async function doFetch(method, path, init) {
269
279
  try {
270
280
  const requestInit = {
271
281
  method,
@@ -274,13 +284,55 @@ export function createOpenclawClawlingApiClient(opts) {
274
284
  if (init?.body !== undefined) {
275
285
  requestInit.body = init.body;
276
286
  }
277
- res = await fetchImpl(url(path, init?.baseUrl || undefined), requestInit);
287
+ return await fetchImpl(url(path, init?.baseUrl || undefined), requestInit);
278
288
  }
279
289
  catch (err) {
280
290
  throw new ClawlingApiError("transport", `fetch failed: ${err instanceof Error ? err.message : String(err)}`, { path });
281
291
  }
292
+ }
293
+ async function call(method, path, init) {
294
+ const res = await doFetch(method, path, init);
282
295
  return await readEnvelope(res, path);
283
296
  }
297
+ /**
298
+ * Orchestration routes (`${ORCH}/...`) are always HTTP 200 with the
299
+ * business code inside the envelope — a `21003` permission denial is a
300
+ * normal outcome there, not a transport failure. Unlike `call<T>`, this
301
+ * does NOT unwrap `data` or throw on a non-zero `code`; it hands back the
302
+ * parsed envelope exactly as received so the caller can branch on `code`
303
+ * itself.
304
+ *
305
+ * A 401/403 is checked FIRST, before any body parsing, exactly like
306
+ * `readEnvelope` — an expired/malformed token is not a business-code
307
+ * outcome and must throw `kind: "auth"` rather than fall through to the
308
+ * empty-body branch below and be misread as success. NOTE: on the
309
+ * orchestration tool path (`tools.ts` `buildClient` → a raw client,
310
+ * consumed via `withClient`) this throw is NOT retried — `withClient`
311
+ * turns any `ClawlingApiError` straight into `apiError(err)`. Reactive
312
+ * retry-after-refresh only exists behind `getConversationApiClient`'s
313
+ * `withRefresh` wrapper (`runtime.ts`), which the twelve `orch*` tools do
314
+ * not go through; this matches the other non-orchestration tools that
315
+ * build a raw client the same way. Proactive rotation is still picked up here for
316
+ * free, since the token is read fresh from the account at call time.
317
+ */
318
+ async function orchCall(method, path, init) {
319
+ const res = await doFetch(method, path, init);
320
+ if (res.status === 401 || res.status === 403) {
321
+ throw new ClawlingApiError("auth", `unauthorized (status ${res.status})`, {
322
+ status: res.status,
323
+ path,
324
+ });
325
+ }
326
+ const text = await res.text().catch(() => "");
327
+ if (!text)
328
+ return undefined;
329
+ try {
330
+ return JSON.parse(text);
331
+ }
332
+ catch {
333
+ throw new ClawlingApiError("transport", "non-JSON response", { status: res.status, path });
334
+ }
335
+ }
284
336
  function parseUploadResult(data, path) {
285
337
  const obj = data;
286
338
  const validKind = obj?.kind === "image" || obj?.kind === "file" || obj?.kind === "audio" || obj?.kind === "video";
@@ -376,6 +428,51 @@ export function createOpenclawClawlingApiClient(opts) {
376
428
  assertNonBlankId(friendUserId, "removeFriend: friendUserId");
377
429
  return await call("DELETE", `/v1/friendships/${encodeURIComponent(friendUserId)}`);
378
430
  },
431
+ async orchListAgents() {
432
+ return await orchCall("GET", `${ORCH}/agents`);
433
+ },
434
+ async orchGetAgent(agentId) {
435
+ return await orchCall("GET", `${ORCH}/agents/${encodeURIComponent(agentId)}`);
436
+ },
437
+ async orchSetAgentBehavior(agentId, behavior) {
438
+ return await orchCall("PATCH", `${ORCH}/agents/${encodeURIComponent(agentId)}`, orchJson({ behavior }));
439
+ },
440
+ async orchListGroups() {
441
+ return await orchCall("GET", `${ORCH}/groups`);
442
+ },
443
+ async orchGetGroup(cid) {
444
+ return await orchCall("GET", `${ORCH}/groups/${encodeURIComponent(cid)}`);
445
+ },
446
+ async orchSetGroupPrompt(cid, description) {
447
+ return await orchCall("PATCH", `${ORCH}/groups/${encodeURIComponent(cid)}`, orchJson({ description }));
448
+ },
449
+ async orchCreateGroup(title, agentIds) {
450
+ return await orchCall("POST", `${ORCH}/groups`, orchJson({ title, agent_ids: agentIds }));
451
+ },
452
+ async orchAddGroupMember(cid, agentId) {
453
+ return await orchCall("POST", `${ORCH}/groups/${encodeURIComponent(cid)}/members`, orchJson({ agent_id: agentId }));
454
+ },
455
+ async orchRemoveGroupMember(cid, agentId) {
456
+ return await orchCall("DELETE", `${ORCH}/groups/${encodeURIComponent(cid)}/members/${encodeURIComponent(agentId)}`);
457
+ },
458
+ async orchSetGroupAgentSettings(cid, agentId, patch) {
459
+ // `!== undefined` rather than truthiness: `muted: false` is a real value
460
+ // the backend must see; an absent key means "leave unchanged".
461
+ const body = {};
462
+ if (patch.muted !== undefined)
463
+ body.muted = patch.muted;
464
+ if (patch.replyMode !== undefined)
465
+ body.reply_mode = patch.replyMode;
466
+ if (patch.batchDelaySeconds !== undefined)
467
+ body.batch_delay_seconds = patch.batchDelaySeconds;
468
+ return await orchCall("PATCH", `${ORCH}/groups/${encodeURIComponent(cid)}/agents/${encodeURIComponent(agentId)}`, orchJson(body));
469
+ },
470
+ async orchCreateConnectCode() {
471
+ return await orchCall("POST", `${ORCH}/connect-codes`);
472
+ },
473
+ async orchGetConnectCode(code) {
474
+ return await orchCall("GET", `${ORCH}/connect-codes/${encodeURIComponent(code)}`);
475
+ },
379
476
  async registerApp(params) {
380
477
  assertNonBlankId(params.appId, "registerApp: appId");
381
478
  return await call("POST", "/v1/agents/me/apps", {
@@ -66,7 +66,7 @@ export const OFFICIAL_SKILLS_BASE = "https://raw.githubusercontent.com/clawling/
66
66
  * in the install-cli repo, bump this constant, ship it. `liveware-sample.ts`
67
67
  * imports the same ref, so the `livewares` tree at that tag is pinned too.
68
68
  */
69
- export const DEFAULT_SKILLS_REF = "skills-v1.13.0";
69
+ export const DEFAULT_SKILLS_REF = "skills-v1.15.0";
70
70
  /** Refuse to treat an absurdly large response as a skill file (defence in depth). */
71
71
  export const MAX_SKILL_BYTES = 256 * 1024;
72
72
  /** This adapter's host target inside `skills/manifest.json`. */
@@ -284,3 +284,54 @@ export const ClawchatUnregisterAppSchema = Type.Object({
284
284
  appId: Type.String({ minLength: 1, description: "The liveware app id to unregister." }),
285
285
  });
286
286
  export const ClawchatLivewareLoginSchema = Type.Object({});
287
+ // --- cloud orchestration (`agent.orchestrate`) ----------------------------
288
+ // Twelve schemas, 1:1 with docs/features/agentorch.md.
289
+ const OrchAgentId = Type.String({
290
+ minLength: 1,
291
+ description: "Explicit agent id from clawchat_orchestrate_list_agents. Never guess one from a nickname.",
292
+ });
293
+ const OrchConversationId = Type.String({
294
+ minLength: 1,
295
+ description: "Explicit group conversation id from clawchat_orchestrate_list_groups.",
296
+ });
297
+ export const ClawchatOrchListAgentsSchema = Type.Object({}, { additionalProperties: false });
298
+ export const ClawchatOrchGetAgentSchema = Type.Object({ agentId: OrchAgentId }, { additionalProperties: false });
299
+ export const ClawchatOrchSetAgentBehaviorSchema = Type.Object({
300
+ agentId: OrchAgentId,
301
+ behavior: Type.String({
302
+ maxLength: 3000,
303
+ description: "The complete new system prompt. REPLACES the whole field — GET first and send the full value.",
304
+ }),
305
+ }, { additionalProperties: false });
306
+ export const ClawchatOrchListGroupsSchema = Type.Object({}, { additionalProperties: false });
307
+ export const ClawchatOrchGetGroupSchema = Type.Object({ conversationId: OrchConversationId }, { additionalProperties: false });
308
+ export const ClawchatOrchSetGroupPromptSchema = Type.Object({
309
+ conversationId: OrchConversationId,
310
+ description: Type.String({
311
+ maxLength: 3000,
312
+ description: "The complete new group system prompt. REPLACES the whole field.",
313
+ }),
314
+ }, { additionalProperties: false });
315
+ export const ClawchatOrchCreateGroupSchema = Type.Object({
316
+ title: Type.String({ minLength: 1, maxLength: 60, description: "Group title, 1-60 runes." }),
317
+ agentIds: Type.Array(Type.String({ minLength: 1 }), {
318
+ minItems: 1,
319
+ description: "Agent ids from clawchat_orchestrate_list_agents. Must be the owner's own agents.",
320
+ }),
321
+ }, { additionalProperties: false });
322
+ export const ClawchatOrchGroupMemberSchema = Type.Object({ conversationId: OrchConversationId, agentId: OrchAgentId }, { additionalProperties: false });
323
+ export const ClawchatOrchSetGroupAgentSettingsSchema = Type.Object({
324
+ conversationId: OrchConversationId,
325
+ agentId: OrchAgentId,
326
+ muted: Type.Optional(Type.Boolean({ description: "Mute or unmute this agent in this group." })),
327
+ replyMode: Type.Optional(Type.Union([Type.Literal("all"), Type.Literal("mention")], {
328
+ description: "all = speaks on every message; mention = only when @-mentioned.",
329
+ })),
330
+ batchDelaySeconds: Type.Optional(Type.Integer({
331
+ minimum: 1,
332
+ maximum: 3600,
333
+ description: "Seconds this agent batches messages before replying. Default 10. Pacing, not a limit.",
334
+ })),
335
+ }, { additionalProperties: false, minProperties: 3 });
336
+ export const ClawchatOrchCreateConnectCodeSchema = Type.Object({}, { additionalProperties: false });
337
+ export const ClawchatOrchGetConnectCodeSchema = Type.Object({ code: Type.String({ minLength: 1, description: "The connect code to look up." }) }, { additionalProperties: false });