@openclaw/clickclack 2026.9.8 → 2026.10.1-beta.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,11 +1,11 @@
1
1
  import { n as __reExport, t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
2
- import { c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, f as isClickClackAccountCurrent, g as resolveClickClackAccountConfig, h as resolveClickClackAccount, l as clickClackConfigAdapter, m as listEnabledClickClackAccounts, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BW1Q06w5.mjs";
3
- import { i as createClickClackClient, n as resolveWorkspaceId, o as normalizeClickClackCorrelationId, r as ClickClackHttpError, t as resolveChannelId } from "./resolve-BHGLohni.mjs";
4
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-BKkzzjBA.mjs";
2
+ import { c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, f as isClickClackAccountCurrent, g as resolveClickClackAccountConfig, h as resolveClickClackAccount, l as clickClackConfigAdapter, m as listEnabledClickClackAccounts, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-DcF_7-VV.mjs";
3
+ import { a as createClickClackClient, i as ClickClackHttpError, n as resolveChannelId, r as resolveWorkspaceId, s as normalizeClickClackCorrelationId } from "./resolve-DPtjHQHt.mjs";
4
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-CF7TKtkU.mjs";
5
5
  import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/account-helpers";
6
6
  import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
7
7
  import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
8
- import { readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
8
+ import { normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
10
10
  import { buildChannelProgressDraftLine, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, deriveDurableFinalDeliveryRequirements, formatChannelProgressDraftLineForEntry, isCompleteAgentPreamble } from "openclaw/plugin-sdk/channel-outbound";
11
11
  import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
@@ -18,13 +18,15 @@ import { parseAccessGroupAllowFromEntry } from "openclaw/plugin-sdk/access-group
18
18
  import { formatNormalizedAllowFromEntries, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
19
19
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
20
20
  import { createHash, randomUUID } from "node:crypto";
21
+ import { createAsyncLock } from "openclaw/plugin-sdk/async-lock-runtime";
21
22
  import { truncateCodePoints } from "openclaw/plugin-sdk/text-utility-runtime";
22
23
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
23
24
  import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
24
25
  import { buildChannelInboundEventContext, createChannelInboundEnvelopeBuilder, createChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression } from "openclaw/plugin-sdk/channel-inbound";
25
26
  import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
26
27
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
27
- import { FormatCapabilityProfile, renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
28
+ import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
29
+ import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
28
30
  //#region src/channels/mention-gating.ts
29
31
  function resolveBotThreadMentionPolicy(params) {
30
32
  if (!params.isBotOwnedThread || params.requireMentionInBotThreads === void 0) return {
@@ -56,14 +58,12 @@ function withGenerationStore(runtime, run) {
56
58
  owner = {
57
59
  store,
58
60
  ...!store.observe || !store.compareAndApply ? { native: runtime.state.openSyncKeyedStore(GENERATION_STORE_OPTIONS) } : {},
59
- tail: Promise.resolve()
61
+ withLock: createAsyncLock()
60
62
  };
61
63
  storesByRuntime$2.set(runtime, owner);
62
64
  }
63
65
  const currentOwner = owner;
64
- const current = owner.tail.then(() => run(currentOwner));
65
- owner.tail = current.then(() => void 0, () => void 0);
66
- return current;
66
+ return currentOwner.withLock(() => run(currentOwner));
67
67
  }
68
68
  function mutateGeneration(runtime, sessionKey, decide) {
69
69
  return withGenerationStore(runtime, async ({ store, native }) => {
@@ -248,12 +248,7 @@ function attachBindingToCurrentActiveSession(params) {
248
248
  readConsistency: "latest"
249
249
  });
250
250
  if (!entry?.sessionId || entry.archivedAt !== void 0) return;
251
- if (entry.sessionId === params.binding.sessionId) {
252
- if (params.binding.detachedAt === void 0) return params.binding;
253
- const { detachedAt: _detachedAt, ...attached } = params.binding;
254
- params.store.set(params.sessionKey, attached);
255
- return attached;
256
- }
251
+ if (entry.sessionId === params.binding.sessionId && params.binding.detachedAt === void 0) return params.binding;
257
252
  const { detachedAt: _detachedAt, ...retained } = params.binding;
258
253
  const attached = {
259
254
  ...retained,
@@ -546,12 +541,6 @@ function resolveClickClackGroupPolicy(params) {
546
541
  }
547
542
  //#endregion
548
543
  //#region extensions/clickclack/src/mention-facts.ts
549
- /**
550
- * Detects whether a ClickClack group message contains a direct mention of the
551
- * current account.
552
- *
553
- * Pure helper – no side effects, no runtime imports.
554
- */
555
544
  const CLICKCLACK_MENTION_PATTERN = /(?:^|[^a-z0-9_@-])@([a-z0-9][a-z0-9_-]{1,31})(?![a-z0-9_-])/giu;
556
545
  function buildLocalMentionRegexes(params) {
557
546
  if (params.mentionPatterns.length === 0) return [];
@@ -574,18 +563,6 @@ function buildLocalMentionRegexes(params) {
574
563
  function resolveMentionHandles(body) {
575
564
  return [...body.matchAll(CLICKCLACK_MENTION_PATTERN)].map((match) => match[1]?.toLowerCase()).filter((handle) => Boolean(handle));
576
565
  }
577
- /**
578
- * Builds mention facts for a ClickClack message.
579
- *
580
- * Rules:
581
- * - DMs always have canDetectMention: false, wasMentioned: false
582
- * (DMs bypass mention gating).
583
- * - Group messages: canDetectMention: true when body text is available.
584
- * - Checks the message body against shared and account-local mention patterns.
585
- * - If botHandle is provided and the message body contains its ClickClack
586
- * `@handle`, treat it as a mention.
587
- * - Shared patterns default to the routed agent's identity name when none are configured.
588
- */
589
566
  function resolveClickClackMentionFacts(params) {
590
567
  const { isDirect, body, mentionPatterns, botHandle, cfg, agentId, channelId } = params;
591
568
  if (isDirect) return {
@@ -783,10 +760,6 @@ async function resolvePreparedInboundRoute(params) {
783
760
  revoked: discussionResolution.state === "revoked"
784
761
  };
785
762
  }
786
- /**
787
- * Resolves whether a ClickClack message should enter the agent pipeline and
788
- * whether its command-style body may run tools.
789
- */
790
763
  async function resolveClickClackInboundAccess(params) {
791
764
  const runtime = getClickClackRuntime();
792
765
  const rootPolicyConfigured = resolveClickClackGroupPolicy({
@@ -1024,11 +997,6 @@ async function syncClickClackCommandMenu(params) {
1024
997
  }
1025
998
  //#endregion
1026
999
  //#region extensions/clickclack/src/activity.ts
1027
- /**
1028
- * Publishes agent activity (streamed commentary + tool progress) into
1029
- * ClickClack as durable `agent_commentary` / `agent_tool` message rows,
1030
- * coalesced so one logical step becomes one row instead of a row per frame.
1031
- */
1032
1000
  const CLICKCLACK_COMMENTARY_FLUSH_MS = 700;
1033
1001
  /** Item kinds rendered as agent_tool rows; everything else is commentary. */
1034
1002
  const TOOL_ITEM_KINDS = /* @__PURE__ */ new Set([
@@ -1067,18 +1035,9 @@ function commentaryBody(payload) {
1067
1035
  }
1068
1036
  function activityBody(payload) {
1069
1037
  const line = formatChannelProgressDraftLineForEntry(void 0, {
1038
+ ...payload,
1070
1039
  event: "item",
1071
- itemId: payload.itemId,
1072
- toolCallId: payload.toolCallId,
1073
- itemKind: payload.kind,
1074
- title: payload.title,
1075
- name: payload.name,
1076
- phase: payload.phase,
1077
- status: payload.status,
1078
- summary: payload.summary,
1079
- progressText: payload.progressText,
1080
- meta: payload.meta,
1081
- commandBearing: payload.commandBearing
1040
+ itemKind: payload.kind
1082
1041
  })?.trim();
1083
1042
  if (line) return line;
1084
1043
  const head = payload.name?.trim() || payload.title?.trim();
@@ -1229,28 +1188,7 @@ function createClickClackActivityPublisher(params) {
1229
1188
  }
1230
1189
  //#endregion
1231
1190
  //#region extensions/clickclack/src/outbound.ts
1232
- /**
1233
- * Outbound ClickClack delivery helpers for channel messages, thread replies,
1234
- * and direct messages.
1235
- */
1236
1191
  const CLICKCLACK_MAX_UPLOAD_BYTES = 67108864;
1237
- const CLICKCLACK_FORMAT_PROFILE = FormatCapabilityProfile.define({
1238
- mechanism: "markdown",
1239
- chunk: {
1240
- limit: 1048576,
1241
- unit: "bytes"
1242
- }
1243
- });
1244
- function renderClickClackMarkdown(markdown) {
1245
- return renderMarkdownWithMarkers({
1246
- text: markdown,
1247
- styles: [],
1248
- links: []
1249
- }, {
1250
- styleMarkers: {},
1251
- escapeText: (text) => text
1252
- }, CLICKCLACK_FORMAT_PROFILE);
1253
- }
1254
1192
  async function createTargetMessage(params) {
1255
1193
  const parsed = parseClickClackTarget(params.to);
1256
1194
  const explicitThreadId = params.threadId == null ? "" : String(params.threadId);
@@ -1340,7 +1278,7 @@ function createOutboundContext(params) {
1340
1278
  * message id, or undefined when sanitization removes all content.
1341
1279
  */
1342
1280
  async function sendClickClackText(params) {
1343
- const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text));
1281
+ const text = sanitizeAssistantVisibleText(params.text);
1344
1282
  if (!text) return;
1345
1283
  const { account, client } = createOutboundContext(params);
1346
1284
  return (await createTargetMessage({
@@ -1351,32 +1289,25 @@ async function sendClickClackText(params) {
1351
1289
  threadId: params.threadId,
1352
1290
  replyToId: params.replyToId,
1353
1291
  provenance: params.provenance,
1354
- nonce: textDeliveryNonce({
1355
- deliveryQueueId: params.deliveryQueueId,
1356
- deliveryPartIndex: params.deliveryPartIndex
1357
- }),
1292
+ nonce: textDeliveryNonce(params),
1358
1293
  onPlatformSendDispatch: params.onPlatformSendDispatch,
1359
1294
  assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1360
1295
  })).id;
1361
1296
  }
1362
- /** Resolves, uploads, sends, then attaches one file to a ClickClack message. */
1363
1297
  async function sendClickClackMedia(params) {
1364
- const nonces = mediaDeliveryNonces({
1365
- deliveryQueueId: params.deliveryQueueId,
1366
- deliveryPartIndex: params.deliveryPartIndex
1367
- });
1298
+ const nonces = mediaDeliveryNonces(params);
1368
1299
  const { account, client } = createOutboundContext(params);
1369
- const maxBytes = Math.min(resolveChannelMediaMaxBytes({
1370
- cfg: params.cfg,
1371
- accountId: account.accountId,
1372
- resolveChannelLimitMb: () => account.config.mediaMaxMb
1373
- }) ?? CLICKCLACK_MAX_UPLOAD_BYTES, CLICKCLACK_MAX_UPLOAD_BYTES);
1374
- const preloadedMedia = nonces.upload ? void 0 : await loadOutboundMediaFromUrl(params.mediaUrl, {
1375
- maxBytes,
1300
+ const mediaLoadOptions = {
1301
+ maxBytes: Math.min(resolveChannelMediaMaxBytes({
1302
+ cfg: params.cfg,
1303
+ accountId: account.accountId,
1304
+ resolveChannelLimitMb: () => account.config.mediaMaxMb
1305
+ }) ?? CLICKCLACK_MAX_UPLOAD_BYTES, CLICKCLACK_MAX_UPLOAD_BYTES),
1376
1306
  mediaAccess: params.mediaAccess,
1377
1307
  mediaLocalRoots: params.mediaLocalRoots,
1378
1308
  mediaReadFile: params.mediaReadFile
1379
- });
1309
+ };
1310
+ const preloadedMedia = nonces.upload ? void 0 : await loadOutboundMediaFromUrl(params.mediaUrl, mediaLoadOptions);
1380
1311
  const workspaceId = await resolveWorkspaceId(client, account.workspace);
1381
1312
  let upload = nonces.upload ? await client.findUploadByNonce({
1382
1313
  workspaceId,
@@ -1384,12 +1315,7 @@ async function sendClickClackMedia(params) {
1384
1315
  }) : void 0;
1385
1316
  let mediaFilename = preloadedMedia?.fileName?.trim();
1386
1317
  if (!upload) {
1387
- const media = preloadedMedia ?? await loadOutboundMediaFromUrl(params.mediaUrl, {
1388
- maxBytes,
1389
- mediaAccess: params.mediaAccess,
1390
- mediaLocalRoots: params.mediaLocalRoots,
1391
- mediaReadFile: params.mediaReadFile
1392
- });
1318
+ const media = preloadedMedia ?? await loadOutboundMediaFromUrl(params.mediaUrl, mediaLoadOptions);
1393
1319
  const contentType = media.contentType?.trim() || "application/octet-stream";
1394
1320
  const filename = media.fileName?.trim() || `attachment${extensionForMime(contentType) ?? ""}`;
1395
1321
  mediaFilename = filename;
@@ -1401,7 +1327,7 @@ async function sendClickClackMedia(params) {
1401
1327
  ...nonces.upload ? { nonce: nonces.upload } : {}
1402
1328
  });
1403
1329
  }
1404
- const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text)) || mediaFilename || upload.filename || "attachment";
1330
+ const text = sanitizeAssistantVisibleText(params.text) || mediaFilename || upload.filename || "attachment";
1405
1331
  const message = await createTargetMessage({
1406
1332
  client,
1407
1333
  workspaceId,
@@ -1442,12 +1368,6 @@ async function sendClickClackMedia(params) {
1442
1368
  }
1443
1369
  return message.id;
1444
1370
  }
1445
- function collectReconciliationMediaUrls(ctx) {
1446
- const planned = ctx.renderedBatchPlan?.items[0]?.mediaUrls;
1447
- if (planned?.length) return planned.map((url) => url.trim()).filter(Boolean);
1448
- const payload = ctx.payloads[0];
1449
- return [payload?.mediaUrl, ...payload?.mediaUrls ?? []].map((url) => url?.trim()).filter((url) => Boolean(url));
1450
- }
1451
1371
  /**
1452
1372
  * Completes an unknown durable send through ClickClack's message/upload nonces.
1453
1373
  * Media recovery never rereads the original source after restart.
@@ -1457,14 +1377,15 @@ async function reconcileClickClackUnknownSend(ctx) {
1457
1377
  status: "unresolved",
1458
1378
  error: "ClickClack reconciliation requires exactly one payload"
1459
1379
  };
1460
- const mediaUrls = collectReconciliationMediaUrls(ctx);
1380
+ const payload = ctx.payloads[0];
1381
+ const plannedMediaUrls = ctx.renderedBatchPlan?.items[0]?.mediaUrls;
1382
+ const mediaUrls = normalizeTrimmedStringList(plannedMediaUrls?.length ? plannedMediaUrls : [payload?.mediaUrl, ...payload?.mediaUrls ?? []]);
1461
1383
  const { account, client } = createOutboundContext({
1462
1384
  cfg: ctx.cfg,
1463
1385
  accountId: ctx.accountId
1464
1386
  });
1465
1387
  const workspaceId = await resolveWorkspaceId(client, account.workspace);
1466
1388
  const effectiveReplyToId = ctx.effectiveReplyToId !== void 0 ? ctx.effectiveReplyToId : ctx.replyToMode === "off" ? void 0 : ctx.replyToId;
1467
- const payload = ctx.payloads[0];
1468
1389
  const caption = ctx.renderedBatchPlan?.items[0]?.text ?? payload?.text ?? "";
1469
1390
  if (mediaUrls.length === 0) {
1470
1391
  const nonce = textDeliveryNonce({
@@ -1548,11 +1469,6 @@ async function reconcileClickClackUnknownSend(ctx) {
1548
1469
  }
1549
1470
  //#endregion
1550
1471
  //#region extensions/clickclack/src/progress.ts
1551
- /**
1552
- * Publishes ClickClack's native ephemeral agent.progress signal for one
1553
- * OpenClaw turn. ClickClack renders this as its compact "Agent is
1554
- * responding" status and the detailed progress lines above the composer.
1555
- */
1556
1472
  function normalizedKind(payload) {
1557
1473
  const kind = payload.kind?.trim().toLowerCase();
1558
1474
  if (!kind || kind === "preamble" || kind === "analysis" || kind === "thinking" || kind === "reasoning" || kind === "missing") return "commentary";
@@ -1561,18 +1477,9 @@ function normalizedKind(payload) {
1561
1477
  function progressText(payload) {
1562
1478
  if (payload.kind !== "preamble" && payload.title) return payload.title;
1563
1479
  const line = buildChannelProgressDraftLine({
1480
+ ...payload,
1564
1481
  event: "item",
1565
- itemId: payload.itemId,
1566
- toolCallId: payload.toolCallId,
1567
- itemKind: payload.kind,
1568
- title: payload.title,
1569
- name: payload.name,
1570
- phase: payload.phase,
1571
- status: payload.status,
1572
- summary: payload.summary,
1573
- progressText: payload.progressText,
1574
- meta: payload.meta,
1575
- commandBearing: payload.commandBearing
1482
+ itemKind: payload.kind
1576
1483
  })?.text?.trim();
1577
1484
  if (line) return line;
1578
1485
  return payload.progressText?.trim() || payload.summary?.trim() || payload.title?.trim() || payload.name?.trim() || payload.meta?.trim() || payload.status?.trim() || "Working";
@@ -1590,15 +1497,15 @@ function createLineIdResolver() {
1590
1497
  if (identity) return `item:${identity}`;
1591
1498
  const kind = normalizedKind(payload);
1592
1499
  const anonymousLines = anonymousLinesByKind.get(kind) ?? [];
1593
- const line = (payload.phase?.trim().toLowerCase() === "start" ? void 0 : anonymousLines.toReversed().find((line) => line.active)) ?? (() => {
1594
- const created = {
1595
- id: `item:${kind}:${++anonymousSequence}`,
1596
- active: true
1597
- };
1598
- anonymousLines.push(created);
1500
+ const existingAnonymous = payload.phase?.trim().toLowerCase() === "start" ? void 0 : anonymousLines.toReversed().find((line) => line.active);
1501
+ const line = existingAnonymous ?? {
1502
+ id: `item:${kind}:${++anonymousSequence}`,
1503
+ active: true
1504
+ };
1505
+ if (!existingAnonymous) {
1506
+ anonymousLines.push(line);
1599
1507
  anonymousLinesByKind.set(kind, anonymousLines);
1600
- return created;
1601
- })();
1508
+ }
1602
1509
  if (isFinal(payload)) line.active = false;
1603
1510
  return line.id;
1604
1511
  };
@@ -1661,18 +1568,11 @@ function createClickClackAgentProgressPublisher(params) {
1661
1568
  queue.splice(0, queue.length, ...controlFrames);
1662
1569
  };
1663
1570
  const waitForDrainWithinFinalizeGrace = async (pending) => {
1664
- let timeout;
1665
1571
  let drained = false;
1666
- try {
1667
- await Promise.race([pending.then(() => {
1668
- drained = true;
1669
- }), new Promise((resolve) => {
1670
- timeout = setTimeout(resolve, CLICKCLACK_PROGRESS_FINALIZE_GRACE_MS);
1671
- })]);
1672
- return drained;
1673
- } finally {
1674
- if (timeout) clearTimeout(timeout);
1675
- }
1572
+ await raceWithTimeout(pending.then(() => {
1573
+ drained = true;
1574
+ }), CLICKCLACK_PROGRESS_FINALIZE_GRACE_MS, () => void 0);
1575
+ return drained;
1676
1576
  };
1677
1577
  const scheduleLineDrain = () => {
1678
1578
  if (lineDrainTimer) return;
@@ -9,10 +9,6 @@ const WebSocket = require(path.join(path.dirname(require.resolve("ws/package.jso
9
9
  //#endregion
10
10
  //#region extensions/clickclack/src/http-client.ts
11
11
  /**
12
- * Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
13
- * delivery code.
14
- */
15
- /**
16
12
  * Serializes optional provenance into the wire fields. Unknown JSON fields
17
13
  * are ignored by servers without the provenance columns, so these are safe
18
14
  * to send unconditionally when present.
@@ -60,9 +56,6 @@ function normalizeClickClackCorrelationId(value) {
60
56
  if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
61
57
  return normalized;
62
58
  }
63
- /**
64
- * Creates a typed client for the ClickClack API using bearer-token auth.
65
- */
66
59
  function createClickClackClient(options) {
67
60
  const baseUrl = options.baseUrl.replace(/\/$/, "");
68
61
  const fetcher = options.fetch ?? fetch;
@@ -99,6 +92,9 @@ function createClickClackClient(options) {
99
92
  if (timeout) clearTimeout(timeout);
100
93
  }
101
94
  }
95
+ async function requestObject(key, path, init) {
96
+ return (await request(path, init))[key];
97
+ }
102
98
  async function fetchEventPage(workspaceId, pageOptions = {}) {
103
99
  const query = new URLSearchParams({ workspace_id: workspaceId });
104
100
  if (pageOptions.afterCursor) query.set("after_cursor", pageOptions.afterCursor);
@@ -111,7 +107,7 @@ function createClickClackClient(options) {
111
107
  };
112
108
  }
113
109
  async function createMessage(path, body, opts) {
114
- return (await request(path, {
110
+ return requestObject("message", path, {
115
111
  method: "POST",
116
112
  body: JSON.stringify({
117
113
  body,
@@ -119,7 +115,7 @@ function createClickClackClient(options) {
119
115
  ...opts?.nonce ? { nonce: opts.nonce } : {},
120
116
  ...provenanceFields(opts?.provenance)
121
117
  })
122
- })).message;
118
+ });
123
119
  }
124
120
  async function findByNonce(kind, params) {
125
121
  const query = new URLSearchParams({
@@ -137,36 +133,22 @@ function createClickClackClient(options) {
137
133
  }
138
134
  }
139
135
  return {
140
- me: async () => {
141
- return (await request("/api/me")).user;
142
- },
143
- setBotCommands: async (commands) => {
144
- return (await request("/api/bots/self/commands", {
145
- method: "PUT",
146
- body: JSON.stringify({ commands })
147
- })).bot_commands;
148
- },
149
- workspaces: async () => {
150
- return (await request("/api/workspaces")).workspaces;
151
- },
152
- channels: async (workspaceId) => {
153
- return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
154
- },
155
- createChannel: async (workspaceId, channel) => {
156
- return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
157
- method: "POST",
158
- body: JSON.stringify(channel)
159
- })).channel;
160
- },
161
- updateChannel: async (channelId, patch) => {
162
- return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
163
- method: "PATCH",
164
- body: JSON.stringify(patch)
165
- })).channel;
166
- },
167
- channelMessages: async (channelId, afterSeq, limit = 20) => {
168
- return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
169
- },
136
+ me: async () => requestObject("user", "/api/me"),
137
+ setBotCommands: async (commands) => requestObject("bot_commands", "/api/bots/self/commands", {
138
+ method: "PUT",
139
+ body: JSON.stringify({ commands })
140
+ }),
141
+ workspaces: async () => requestObject("workspaces", "/api/workspaces"),
142
+ channels: async (workspaceId) => requestObject("channels", `/api/workspaces/${encodeURIComponent(workspaceId)}/channels`),
143
+ createChannel: async (workspaceId, channel) => requestObject("channel", `/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
144
+ method: "POST",
145
+ body: JSON.stringify(channel)
146
+ }),
147
+ updateChannel: async (channelId, patch) => requestObject("channel", `/api/channels/${encodeURIComponent(channelId)}`, {
148
+ method: "PATCH",
149
+ body: JSON.stringify(patch)
150
+ }),
151
+ channelMessages: async (channelId, afterSeq, limit = 20) => requestObject("messages", `/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`),
170
152
  latestChannelMessages: async (channelId, limit = 30) => {
171
153
  const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
172
154
  let beforeSeq;
@@ -209,38 +191,32 @@ function createClickClackClient(options) {
209
191
  beforeSeq = page.oldest_seq;
210
192
  }
211
193
  },
212
- directMessages: async (conversationId, afterSeq, limit = 20) => {
213
- return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
214
- },
194
+ directMessages: async (conversationId, afterSeq, limit = 20) => requestObject("messages", `/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`),
215
195
  thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
216
- message: async (messageId) => {
217
- return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
218
- },
196
+ message: async (messageId) => requestObject("message", `/api/messages/${encodeURIComponent(messageId)}`),
219
197
  findMessageByNonce: async (params) => findByNonce("message", params),
220
198
  createChannelMessage: async (channelId, body, opts) => createMessage(`/api/channels/${encodeURIComponent(channelId)}/messages`, body, opts),
221
199
  createThreadReply: async (messageId, body, opts) => createMessage(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, body, {
222
200
  nonce: opts?.nonce,
223
201
  provenance: opts?.provenance
224
202
  }),
225
- createDirectConversation: async (workspaceId, memberIds) => {
226
- return (await request("/api/dms", {
227
- method: "POST",
228
- body: JSON.stringify({
229
- workspace_id: workspaceId,
230
- member_ids: memberIds
231
- })
232
- })).conversation;
233
- },
203
+ createDirectConversation: async (workspaceId, memberIds) => requestObject("conversation", "/api/dms", {
204
+ method: "POST",
205
+ body: JSON.stringify({
206
+ workspace_id: workspaceId,
207
+ member_ids: memberIds
208
+ })
209
+ }),
234
210
  createUpload: async (params) => {
235
211
  const form = new FormData();
236
212
  const bytes = bufferToBlobPart(params.buffer);
237
213
  form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
238
214
  const query = new URLSearchParams({ workspace_id: params.workspaceId });
239
215
  if (params.nonce) query.set("nonce", params.nonce);
240
- return (await request(`/api/uploads?${query.toString()}`, {
216
+ return requestObject("upload", `/api/uploads?${query.toString()}`, {
241
217
  method: "POST",
242
218
  body: form
243
- })).upload;
219
+ });
244
220
  },
245
221
  findUploadByNonce: async (params) => findByNonce("upload", params),
246
222
  attachUpload: async (messageId, uploadId) => {
@@ -256,7 +232,7 @@ function createClickClackClient(options) {
256
232
  */
257
233
  createActivityMessage: async (params) => {
258
234
  if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
259
- return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
235
+ return requestObject("message", params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
260
236
  method: "POST",
261
237
  body: JSON.stringify({
262
238
  body: params.body,
@@ -264,15 +240,12 @@ function createClickClackClient(options) {
264
240
  turn_id: params.turnId,
265
241
  ...provenanceFields(params.provenance)
266
242
  })
267
- })).message;
268
- },
269
- /** PATCHes the body of an existing message (activity row coalescing). */
270
- updateMessageBody: async (messageId, body) => {
271
- return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
272
- method: "PATCH",
273
- body: JSON.stringify({ body })
274
- })).message;
243
+ });
275
244
  },
245
+ updateMessageBody: async (messageId, body) => requestObject("message", `/api/messages/${encodeURIComponent(messageId)}`, {
246
+ method: "PATCH",
247
+ body: JSON.stringify({ body })
248
+ }),
276
249
  /**
277
250
  * Publishes an ephemeral realtime signal such as native agent progress.
278
251
  * These frames are intentionally not persisted as messages.
@@ -313,19 +286,15 @@ function createClickClackClient(options) {
313
286
  }
314
287
  //#endregion
315
288
  //#region extensions/clickclack/src/resolve.ts
316
- /**
317
- * Resolves a workspace slug/name/id from config to a ClickClack workspace id.
318
- */
289
+ function findClickClackWorkspace(workspaces, selector) {
290
+ return workspaces.find((candidate) => candidate.id === selector || candidate.slug === selector || candidate.name === selector);
291
+ }
319
292
  async function resolveWorkspaceId(client, workspace) {
320
293
  if (workspace.startsWith("wsp_")) return workspace;
321
- const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
294
+ const found = findClickClackWorkspace(await client.workspaces(), workspace);
322
295
  if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
323
296
  return found.id;
324
297
  }
325
- /**
326
- * Resolves a channel name/id from config or target input to a ClickClack
327
- * channel id.
328
- */
329
298
  async function resolveChannelId(client, workspaceId, channel) {
330
299
  if (channel.startsWith("chn_")) return channel;
331
300
  const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
@@ -333,4 +302,4 @@ async function resolveChannelId(client, workspaceId, channel) {
333
302
  return found.id;
334
303
  }
335
304
  //#endregion
336
- export { isClickClackChannelNameConflict as a, createClickClackClient as i, resolveWorkspaceId as n, normalizeClickClackCorrelationId as o, ClickClackHttpError as r, resolveChannelId as t };
305
+ export { createClickClackClient as a, ClickClackHttpError as i, resolveChannelId as n, isClickClackChannelNameConflict as o, resolveWorkspaceId as r, normalizeClickClackCorrelationId as s, findClickClackWorkspace as t };