@openclaw/clickclack 2026.7.2-beta.2 → 2026.7.2-beta.4
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/README.md +120 -0
- package/dist/api.js +2 -2
- package/dist/{channel-BnREQIP8.js → channel-BDq0KWDB.js} +456 -53
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +48 -0
- package/dist/index.js +3 -1
- package/dist/runtime-api-CV1cOqFO.js +782 -0
- package/dist/runtime-api.js +4 -4
- package/dist/setup-claim-CJIUfZS7.js +136 -0
- package/dist/setup-plugin-api.js +2 -1
- package/dist/{setup-surface-B346XaAu.js → setup-surface-Baf26N-D.js} +341 -20
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/npm-shrinkwrap.json +7 -7
- package/openclaw.plugin.json +49 -13
- package/package.json +91 -11
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as clickClackMeta, b as listEnabledClickClackAccounts, c as resolveChannelId, d as createClickClackClient, g as clickClackConfigAdapter, h as CLICKCLACK_CHANNEL_ID, l as resolveWorkspaceId, m as clickClackConfigSchema, n as clickClackSetupAdapter, p as normalizeClickClackCorrelationId, r as clickClackSetupContract, t as clickClackSetupWizard, v as DEFAULT_ACCOUNT_ID, x as resolveClickClackAccount } from "./setup-surface-Baf26N-D.js";
|
|
2
2
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-BKkzzjBA.js";
|
|
3
3
|
import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
4
4
|
import { buildChannelProgressDraftLine, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, deriveDurableFinalDeliveryRequirements } from "openclaw/plugin-sdk/channel-outbound";
|
|
@@ -8,10 +8,11 @@ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
|
8
8
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
9
9
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
10
10
|
import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
|
|
11
|
+
import { createChannelInboundEnvelopeBuilder } from "openclaw/plugin-sdk/channel-inbound";
|
|
11
12
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
13
14
|
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
14
|
-
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
15
|
+
import { renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
15
16
|
//#region extensions/clickclack/src/runtime.ts
|
|
16
17
|
/**
|
|
17
18
|
* Runtime store for host-provided OpenClaw services used by the ClickClack
|
|
@@ -327,6 +328,336 @@ function createClickClackActivityPublisher(params) {
|
|
|
327
328
|
};
|
|
328
329
|
}
|
|
329
330
|
//#endregion
|
|
331
|
+
//#region extensions/clickclack/src/discussions/binding-generation.ts
|
|
332
|
+
const DISCUSSION_GENERATIONS_NAMESPACE = "discussion-binding-generations";
|
|
333
|
+
const MAX_PENDING_DISCUSSION_GENERATIONS = 1e4;
|
|
334
|
+
const storesByRuntime$2 = /* @__PURE__ */ new WeakMap();
|
|
335
|
+
function getGenerationStore(runtime) {
|
|
336
|
+
const existing = storesByRuntime$2.get(runtime);
|
|
337
|
+
if (existing) return existing;
|
|
338
|
+
const created = runtime.state.openSyncKeyedStore({
|
|
339
|
+
namespace: DISCUSSION_GENERATIONS_NAMESPACE,
|
|
340
|
+
maxEntries: MAX_PENDING_DISCUSSION_GENERATIONS,
|
|
341
|
+
overflowPolicy: "reject-new"
|
|
342
|
+
});
|
|
343
|
+
storesByRuntime$2.set(runtime, created);
|
|
344
|
+
return created;
|
|
345
|
+
}
|
|
346
|
+
/** Reserves a generation so an interrupted channel create can be adopted on retry. */
|
|
347
|
+
function reserveDiscussionBindingGeneration(params) {
|
|
348
|
+
const store = getGenerationStore(params.runtime);
|
|
349
|
+
const existing = store.lookup(params.sessionKey);
|
|
350
|
+
if (existing?.destinationIdentity === params.destinationIdentity) return existing.generation;
|
|
351
|
+
const generation = (params.createGeneration ?? randomUUID)();
|
|
352
|
+
store.register(params.sessionKey, {
|
|
353
|
+
destinationIdentity: params.destinationIdentity,
|
|
354
|
+
generation
|
|
355
|
+
});
|
|
356
|
+
return generation;
|
|
357
|
+
}
|
|
358
|
+
/** Clears only the completed reservation; future opens must mint a new ownership ref. */
|
|
359
|
+
function clearDiscussionBindingGeneration(params) {
|
|
360
|
+
const store = getGenerationStore(params.runtime);
|
|
361
|
+
const existing = store.lookup(params.sessionKey);
|
|
362
|
+
if (!existing) return;
|
|
363
|
+
if (params.expectedGeneration && existing.generation !== params.expectedGeneration) return;
|
|
364
|
+
store.delete(params.sessionKey);
|
|
365
|
+
}
|
|
366
|
+
/** Quarantines a destination before the first fallible channel create. */
|
|
367
|
+
function recordPendingDiscussionOpen(params) {
|
|
368
|
+
const store = getGenerationStore(params.runtime);
|
|
369
|
+
const existing = store.lookup(params.sessionKey);
|
|
370
|
+
if (!existing || existing.generation !== params.generation) throw new Error("ClickClack discussion generation changed before channel creation");
|
|
371
|
+
store.register(params.sessionKey, {
|
|
372
|
+
...existing,
|
|
373
|
+
pending: params.pending
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function listPendingDiscussionOpens(runtime) {
|
|
377
|
+
return getGenerationStore(runtime).entries().flatMap((entry) => entry.value.pending ? [{
|
|
378
|
+
sessionKey: entry.key,
|
|
379
|
+
generation: entry.value.generation,
|
|
380
|
+
...entry.value.pending
|
|
381
|
+
}] : []);
|
|
382
|
+
}
|
|
383
|
+
function hasPendingDiscussionOpenForDestination(params) {
|
|
384
|
+
const serverBaseUrl = params.serverBaseUrl.replace(/\/+$/u, "");
|
|
385
|
+
return listPendingDiscussionOpens(params.runtime).some((pending) => pending.serverBaseUrl === serverBaseUrl && pending.workspaceId === params.workspaceId);
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region extensions/clickclack/src/discussions/naming.ts
|
|
389
|
+
function shortSessionHash(sessionKey) {
|
|
390
|
+
return createHash("sha256").update(sessionKey).digest("hex").slice(0, 32);
|
|
391
|
+
}
|
|
392
|
+
function fallbackDiscussionLabel(sessionKey) {
|
|
393
|
+
return `s-${shortSessionHash(sessionKey)}`;
|
|
394
|
+
}
|
|
395
|
+
function resolveDiscussionLabel(label, sessionKey) {
|
|
396
|
+
return label?.trim() || fallbackDiscussionLabel(sessionKey);
|
|
397
|
+
}
|
|
398
|
+
function slugifyDiscussionLabel(label, sessionKey) {
|
|
399
|
+
return label.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80).replace(/-+$/gu, "") || fallbackDiscussionLabel(sessionKey);
|
|
400
|
+
}
|
|
401
|
+
function discussionSessionPeerId(identity) {
|
|
402
|
+
return `disc-${createHash("sha256").update([
|
|
403
|
+
identity.mainSessionKey,
|
|
404
|
+
identity.sessionId,
|
|
405
|
+
identity.serverBaseUrl,
|
|
406
|
+
identity.channelId,
|
|
407
|
+
identity.externalRef
|
|
408
|
+
].join("\0")).digest("hex").slice(0, 32)}`;
|
|
409
|
+
}
|
|
410
|
+
function isDiscussionSessionKey(sessionKey) {
|
|
411
|
+
return sessionKey.includes(":clickclack:") && /:channel:disc-[0-9a-f]{32}$/u.test(sessionKey);
|
|
412
|
+
}
|
|
413
|
+
function discussionExternalRef(installationId, mainSessionKey, sessionId, destinationIdentity, bindingGeneration) {
|
|
414
|
+
return `openclaw:${installationId}:${shortSessionHash([
|
|
415
|
+
mainSessionKey,
|
|
416
|
+
sessionId,
|
|
417
|
+
destinationIdentity,
|
|
418
|
+
bindingGeneration
|
|
419
|
+
].join("\0"))}`;
|
|
420
|
+
}
|
|
421
|
+
function discussionCredentialFingerprint(token) {
|
|
422
|
+
return createHash("sha256").update(token).digest("hex");
|
|
423
|
+
}
|
|
424
|
+
function discussionSessionKey(params) {
|
|
425
|
+
return params.runtime.channel.routing.buildAgentSessionKey({
|
|
426
|
+
agentId: params.agentId,
|
|
427
|
+
channel: "clickclack",
|
|
428
|
+
accountId: params.accountId,
|
|
429
|
+
peer: {
|
|
430
|
+
kind: "channel",
|
|
431
|
+
id: discussionSessionPeerId(params)
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region extensions/clickclack/src/discussions/binding-store.ts
|
|
437
|
+
function bindingMatchesSessionIncarnation(runtime, sessionKey, binding) {
|
|
438
|
+
const entry = runtime.agent.session.getSessionEntry({
|
|
439
|
+
sessionKey,
|
|
440
|
+
readConsistency: "latest"
|
|
441
|
+
});
|
|
442
|
+
return Boolean(entry && binding.sessionId && entry.sessionId === binding.sessionId);
|
|
443
|
+
}
|
|
444
|
+
function bindingMatchesActiveSessionIncarnation(runtime, sessionKey, binding) {
|
|
445
|
+
const entry = runtime.agent.session.getSessionEntry({
|
|
446
|
+
sessionKey,
|
|
447
|
+
readConsistency: "latest"
|
|
448
|
+
});
|
|
449
|
+
return Boolean(entry && binding.sessionId && entry.sessionId === binding.sessionId && entry.archivedAt === void 0);
|
|
450
|
+
}
|
|
451
|
+
const DISCUSSION_BINDINGS_NAMESPACE = "discussion-bindings";
|
|
452
|
+
const MAX_DISCUSSION_BINDINGS = 1e4;
|
|
453
|
+
const storesByRuntime$1 = /* @__PURE__ */ new WeakMap();
|
|
454
|
+
function channelKey(serverBaseUrl, channelId) {
|
|
455
|
+
return `${serverBaseUrl.replace(/\/+$/u, "")}\0${channelId}`;
|
|
456
|
+
}
|
|
457
|
+
/** SQLite-backed session/channel bindings with a process-local inbound lookup index. */
|
|
458
|
+
var ClickClackDiscussionBindingStore = class {
|
|
459
|
+
#store;
|
|
460
|
+
#sessionByChannel = /* @__PURE__ */ new Map();
|
|
461
|
+
#mainByDiscussionSession = /* @__PURE__ */ new Map();
|
|
462
|
+
#runtime;
|
|
463
|
+
constructor(runtime) {
|
|
464
|
+
this.#runtime = runtime;
|
|
465
|
+
this.#store = runtime.state.openSyncKeyedStore({
|
|
466
|
+
namespace: DISCUSSION_BINDINGS_NAMESPACE,
|
|
467
|
+
maxEntries: MAX_DISCUSSION_BINDINGS,
|
|
468
|
+
overflowPolicy: "reject-new"
|
|
469
|
+
});
|
|
470
|
+
for (const entry of this.#store.entries()) this.#index(entry.key, entry.value);
|
|
471
|
+
}
|
|
472
|
+
get(sessionKey) {
|
|
473
|
+
return this.#store.lookup(sessionKey);
|
|
474
|
+
}
|
|
475
|
+
hasCapacity(sessionKey) {
|
|
476
|
+
return this.get(sessionKey) !== void 0 || this.#store.entries().length < MAX_DISCUSSION_BINDINGS;
|
|
477
|
+
}
|
|
478
|
+
getByChannel(serverBaseUrl, channelId) {
|
|
479
|
+
const key = channelKey(serverBaseUrl, channelId);
|
|
480
|
+
const sessionKey = this.#sessionByChannel.get(key);
|
|
481
|
+
if (!sessionKey) return;
|
|
482
|
+
const binding = this.get(sessionKey);
|
|
483
|
+
if (!binding) {
|
|
484
|
+
this.#sessionByChannel.delete(key);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
sessionKey,
|
|
489
|
+
binding
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
set(sessionKey, binding) {
|
|
493
|
+
const previous = this.get(sessionKey);
|
|
494
|
+
this.#store.register(sessionKey, binding);
|
|
495
|
+
if (previous) this.#unindex(sessionKey, previous);
|
|
496
|
+
this.#index(sessionKey, binding);
|
|
497
|
+
}
|
|
498
|
+
delete(sessionKey) {
|
|
499
|
+
const previous = this.get(sessionKey);
|
|
500
|
+
const deleted = this.#store.delete(sessionKey);
|
|
501
|
+
if (deleted && previous) this.#unindex(sessionKey, previous);
|
|
502
|
+
return deleted;
|
|
503
|
+
}
|
|
504
|
+
getByDiscussionSession(sideSessionKey) {
|
|
505
|
+
const sessionKey = this.#mainByDiscussionSession.get(sideSessionKey);
|
|
506
|
+
if (!sessionKey) return;
|
|
507
|
+
const binding = this.get(sessionKey);
|
|
508
|
+
return binding ? {
|
|
509
|
+
sessionKey,
|
|
510
|
+
binding
|
|
511
|
+
} : void 0;
|
|
512
|
+
}
|
|
513
|
+
entries() {
|
|
514
|
+
return this.#store.entries().map((entry) => ({
|
|
515
|
+
sessionKey: entry.key,
|
|
516
|
+
binding: entry.value
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
#index(sessionKey, binding) {
|
|
520
|
+
this.#sessionByChannel.set(channelKey(binding.serverBaseUrl, binding.channelId), sessionKey);
|
|
521
|
+
const sideSessionKey = discussionSessionKey({
|
|
522
|
+
runtime: this.#runtime,
|
|
523
|
+
agentId: binding.agentId,
|
|
524
|
+
mainSessionKey: sessionKey,
|
|
525
|
+
sessionId: binding.sessionId,
|
|
526
|
+
accountId: binding.accountId,
|
|
527
|
+
serverBaseUrl: binding.serverBaseUrl,
|
|
528
|
+
channelId: binding.channelId,
|
|
529
|
+
externalRef: binding.externalRef
|
|
530
|
+
});
|
|
531
|
+
if (sideSessionKey) this.#mainByDiscussionSession.set(sideSessionKey, sessionKey);
|
|
532
|
+
}
|
|
533
|
+
#unindex(sessionKey, binding) {
|
|
534
|
+
this.#sessionByChannel.delete(channelKey(binding.serverBaseUrl, binding.channelId));
|
|
535
|
+
const sideSessionKey = discussionSessionKey({
|
|
536
|
+
runtime: this.#runtime,
|
|
537
|
+
agentId: binding.agentId,
|
|
538
|
+
mainSessionKey: sessionKey,
|
|
539
|
+
sessionId: binding.sessionId,
|
|
540
|
+
accountId: binding.accountId,
|
|
541
|
+
serverBaseUrl: binding.serverBaseUrl,
|
|
542
|
+
channelId: binding.channelId,
|
|
543
|
+
externalRef: binding.externalRef
|
|
544
|
+
});
|
|
545
|
+
if (sideSessionKey) this.#mainByDiscussionSession.delete(sideSessionKey);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
function getClickClackDiscussionBindingStore(runtime) {
|
|
549
|
+
const existing = storesByRuntime$1.get(runtime);
|
|
550
|
+
if (existing) return existing;
|
|
551
|
+
const created = new ClickClackDiscussionBindingStore(runtime);
|
|
552
|
+
storesByRuntime$1.set(runtime, created);
|
|
553
|
+
return created;
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region extensions/clickclack/src/discussions/eligibility.ts
|
|
557
|
+
function discussionAccounts(cfg) {
|
|
558
|
+
return listEnabledClickClackAccounts(cfg).filter((account) => account.configured && account.discussions.enabled);
|
|
559
|
+
}
|
|
560
|
+
function normalizedServerBaseUrl(account) {
|
|
561
|
+
return account.baseUrl.replace(/\/+$/u, "");
|
|
562
|
+
}
|
|
563
|
+
/** Resolves the sole live account and rejects bindings pinned to an older destination. */
|
|
564
|
+
function resolveDiscussionBindingAccount(cfg, binding) {
|
|
565
|
+
const accounts = discussionAccounts(cfg);
|
|
566
|
+
if (accounts.length !== 1) return { state: "unavailable" };
|
|
567
|
+
const account = accounts[0];
|
|
568
|
+
if (!account) return { state: "unavailable" };
|
|
569
|
+
if (account.accountId !== binding.accountId || normalizedServerBaseUrl(account) !== binding.serverBaseUrl || account.discussions.workspace !== binding.workspaceRef || binding.credentialFingerprint !== void 0 && discussionCredentialFingerprint(account.token) !== binding.credentialFingerprint) return {
|
|
570
|
+
state: "stale",
|
|
571
|
+
account
|
|
572
|
+
};
|
|
573
|
+
return {
|
|
574
|
+
state: "active",
|
|
575
|
+
account
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region extensions/clickclack/src/discussions/revoked-channel-store.ts
|
|
580
|
+
const REVOKED_CHANNELS_NAMESPACE = "discussion-revoked-channels";
|
|
581
|
+
const MAX_REVOKED_CHANNELS = 1e5;
|
|
582
|
+
const storesByRuntime = /* @__PURE__ */ new WeakMap();
|
|
583
|
+
function revokedChannelKey(params) {
|
|
584
|
+
return [params.serverBaseUrl.replace(/\/+$/u, ""), params.channelId].join("\0");
|
|
585
|
+
}
|
|
586
|
+
function getStore(runtime) {
|
|
587
|
+
const existing = storesByRuntime.get(runtime);
|
|
588
|
+
if (existing) return existing;
|
|
589
|
+
const created = runtime.state.openSyncKeyedStore({
|
|
590
|
+
namespace: REVOKED_CHANNELS_NAMESPACE,
|
|
591
|
+
maxEntries: MAX_REVOKED_CHANNELS,
|
|
592
|
+
overflowPolicy: "reject-new"
|
|
593
|
+
});
|
|
594
|
+
storesByRuntime.set(runtime, created);
|
|
595
|
+
return created;
|
|
596
|
+
}
|
|
597
|
+
/** Records managed ownership before its live binding is released. */
|
|
598
|
+
function markClickClackDiscussionChannelRevoked(runtime, binding) {
|
|
599
|
+
const value = {
|
|
600
|
+
accountId: binding.accountId,
|
|
601
|
+
serverBaseUrl: binding.serverBaseUrl,
|
|
602
|
+
channelId: binding.channelId,
|
|
603
|
+
revokedAt: Date.now()
|
|
604
|
+
};
|
|
605
|
+
getStore(runtime).register(revokedChannelKey(value), value);
|
|
606
|
+
}
|
|
607
|
+
function markClickClackDiscussionChannelIdentityRevoked(params) {
|
|
608
|
+
const value = {
|
|
609
|
+
accountId: params.accountId,
|
|
610
|
+
serverBaseUrl: params.serverBaseUrl.replace(/\/+$/u, ""),
|
|
611
|
+
channelId: params.channelId,
|
|
612
|
+
revokedAt: Date.now()
|
|
613
|
+
};
|
|
614
|
+
getStore(params.runtime).register(revokedChannelKey(value), value);
|
|
615
|
+
}
|
|
616
|
+
function clearClickClackDiscussionChannelRevoked(params) {
|
|
617
|
+
getStore(params.runtime).delete(revokedChannelKey(params));
|
|
618
|
+
}
|
|
619
|
+
/** Distinguishes a released managed channel from a genuinely ordinary channel. */
|
|
620
|
+
function isClickClackDiscussionChannelRevoked(params) {
|
|
621
|
+
return Boolean(getStore(params.runtime).lookup(revokedChannelKey(params)));
|
|
622
|
+
}
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region extensions/clickclack/src/discussions/routing.ts
|
|
625
|
+
function resolveClickClackDiscussionRoute(params) {
|
|
626
|
+
if (isClickClackDiscussionChannelRevoked(params)) return { state: "revoked" };
|
|
627
|
+
const matched = getClickClackDiscussionBindingStore(params.runtime).getByChannel(params.serverBaseUrl, params.channelId);
|
|
628
|
+
if (!matched) return { state: hasPendingDiscussionOpenForDestination(params) ? "revoked" : "unbound" };
|
|
629
|
+
if (matched.binding.accountId !== params.accountId) return { state: "revoked" };
|
|
630
|
+
if (matched.binding.serverBaseUrl !== params.serverBaseUrl.replace(/\/+$/u, "")) return { state: "revoked" };
|
|
631
|
+
if (matched.binding.archived) return { state: "revoked" };
|
|
632
|
+
if (resolveDiscussionBindingAccount(params.config, matched.binding).state !== "active") return { state: "revoked" };
|
|
633
|
+
if (!bindingMatchesActiveSessionIncarnation(params.runtime, matched.sessionKey, matched.binding)) return { state: "revoked" };
|
|
634
|
+
const sessionKey = discussionSessionKey({
|
|
635
|
+
runtime: params.runtime,
|
|
636
|
+
agentId: matched.binding.agentId,
|
|
637
|
+
mainSessionKey: matched.sessionKey,
|
|
638
|
+
sessionId: matched.binding.sessionId,
|
|
639
|
+
accountId: params.accountId,
|
|
640
|
+
serverBaseUrl: matched.binding.serverBaseUrl,
|
|
641
|
+
channelId: matched.binding.channelId,
|
|
642
|
+
externalRef: matched.binding.externalRef
|
|
643
|
+
});
|
|
644
|
+
if (!sessionKey) return { state: "revoked" };
|
|
645
|
+
return {
|
|
646
|
+
state: "active",
|
|
647
|
+
route: {
|
|
648
|
+
agentId: matched.binding.agentId,
|
|
649
|
+
sessionKey,
|
|
650
|
+
systemPrompt: [
|
|
651
|
+
"You are the side agent for a ClickClack discussion attached to an OpenClaw session.",
|
|
652
|
+
`The main session key is ${matched.sessionKey}.`,
|
|
653
|
+
"Observe it with sessions_history and session_status (using changesSince for incremental checks).",
|
|
654
|
+
"Use sessions_send to relay or steer the main session only when the humans in this discussion ask you to.",
|
|
655
|
+
"These session tools are host-scoped to the attached main session; do not attempt session discovery or alternate targets."
|
|
656
|
+
].join(" ")
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
330
661
|
//#region extensions/clickclack/src/target.ts
|
|
331
662
|
/**
|
|
332
663
|
* Parses `channel:name`, `thread:msg_id`, `dm:usr_id`, or a bare channel name.
|
|
@@ -377,6 +708,42 @@ function looksLikeClickClackTarget(raw) {
|
|
|
377
708
|
* and direct messages.
|
|
378
709
|
*/
|
|
379
710
|
const CLICKCLACK_MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
|
|
711
|
+
const CLICKCLACK_FORMAT_PROFILE = {
|
|
712
|
+
mechanism: "markdown",
|
|
713
|
+
constructs: {
|
|
714
|
+
bold: "native",
|
|
715
|
+
italic: "native",
|
|
716
|
+
underline: "native",
|
|
717
|
+
strikethrough: "native",
|
|
718
|
+
spoiler: "native",
|
|
719
|
+
codeInline: "native",
|
|
720
|
+
codeBlock: "native",
|
|
721
|
+
codeLanguage: "native",
|
|
722
|
+
linkLabel: "native",
|
|
723
|
+
heading: "native",
|
|
724
|
+
bulletList: "native",
|
|
725
|
+
orderedList: "native",
|
|
726
|
+
taskList: "native",
|
|
727
|
+
table: "native",
|
|
728
|
+
blockquote: "native",
|
|
729
|
+
image: "native",
|
|
730
|
+
mention: "native"
|
|
731
|
+
},
|
|
732
|
+
chunk: {
|
|
733
|
+
limit: 1024 * 1024,
|
|
734
|
+
unit: "bytes"
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
function renderClickClackMarkdown(markdown) {
|
|
738
|
+
return renderMarkdownWithMarkers({
|
|
739
|
+
text: markdown,
|
|
740
|
+
styles: [],
|
|
741
|
+
links: []
|
|
742
|
+
}, {
|
|
743
|
+
styleMarkers: {},
|
|
744
|
+
escapeText: (text) => text
|
|
745
|
+
}, CLICKCLACK_FORMAT_PROFILE);
|
|
746
|
+
}
|
|
380
747
|
async function createTargetMessage(params) {
|
|
381
748
|
const parsed = parseClickClackTarget(params.to);
|
|
382
749
|
const explicitThreadId = params.threadId == null ? "" : String(params.threadId);
|
|
@@ -452,7 +819,7 @@ function createOutboundContext(params) {
|
|
|
452
819
|
return {
|
|
453
820
|
account,
|
|
454
821
|
client: createClickClackClient({
|
|
455
|
-
baseUrl: account.
|
|
822
|
+
baseUrl: account.apiEndpoint,
|
|
456
823
|
token: account.token,
|
|
457
824
|
correlationId: params.correlationId
|
|
458
825
|
})
|
|
@@ -463,7 +830,7 @@ function createOutboundContext(params) {
|
|
|
463
830
|
* message id, or undefined when sanitization removes all content.
|
|
464
831
|
*/
|
|
465
832
|
async function sendClickClackText(params) {
|
|
466
|
-
const text = sanitizeAssistantVisibleText(params.text);
|
|
833
|
+
const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text));
|
|
467
834
|
if (!text) return;
|
|
468
835
|
const { account, client } = createOutboundContext(params);
|
|
469
836
|
const workspaceId = await resolveWorkspaceId(client, account.workspace);
|
|
@@ -523,7 +890,7 @@ async function sendClickClackMedia(params) {
|
|
|
523
890
|
...nonces.upload ? { nonce: nonces.upload } : {}
|
|
524
891
|
});
|
|
525
892
|
}
|
|
526
|
-
const text = sanitizeAssistantVisibleText(params.text) || mediaFilename || upload.filename || "attachment";
|
|
893
|
+
const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text)) || mediaFilename || upload.filename || "attachment";
|
|
527
894
|
const message = await createTargetMessage({
|
|
528
895
|
client,
|
|
529
896
|
workspaceId,
|
|
@@ -740,6 +1107,8 @@ async function handleClickClackInbound(params) {
|
|
|
740
1107
|
message
|
|
741
1108
|
});
|
|
742
1109
|
if (!access.shouldDispatch) return;
|
|
1110
|
+
const conversationId = message.channel_id || message.direct_conversation_id;
|
|
1111
|
+
if (!conversationId) return;
|
|
743
1112
|
const isDirect = Boolean(message.direct_conversation_id);
|
|
744
1113
|
const target = buildClickClackTarget(isDirect ? {
|
|
745
1114
|
chatType: "direct",
|
|
@@ -750,13 +1119,29 @@ async function handleClickClackInbound(params) {
|
|
|
750
1119
|
kind: "channel",
|
|
751
1120
|
id: message.channel_id ?? ""
|
|
752
1121
|
});
|
|
753
|
-
const
|
|
1122
|
+
const accountRoute = resolveAccountAgentRoute({
|
|
754
1123
|
cfg: params.config,
|
|
755
1124
|
account: params.account,
|
|
756
1125
|
target,
|
|
757
1126
|
isDirect
|
|
758
1127
|
});
|
|
759
|
-
|
|
1128
|
+
const discussionResolution = !isDirect && message.channel_id ? resolveClickClackDiscussionRoute({
|
|
1129
|
+
runtime,
|
|
1130
|
+
config: params.config,
|
|
1131
|
+
accountId: params.account.accountId,
|
|
1132
|
+
serverBaseUrl: params.account.baseUrl,
|
|
1133
|
+
workspaceId: message.workspace_id,
|
|
1134
|
+
channelId: message.channel_id
|
|
1135
|
+
}) : { state: "unbound" };
|
|
1136
|
+
if (discussionResolution.state === "revoked") return;
|
|
1137
|
+
const discussionRoute = discussionResolution.state === "active" ? discussionResolution.route : void 0;
|
|
1138
|
+
const route = discussionRoute ? {
|
|
1139
|
+
...accountRoute,
|
|
1140
|
+
agentId: discussionRoute.agentId,
|
|
1141
|
+
sessionKey: discussionRoute.sessionKey,
|
|
1142
|
+
lastRoutePolicy: "session"
|
|
1143
|
+
} : accountRoute;
|
|
1144
|
+
if (params.account.replyMode === "model" && !discussionRoute) {
|
|
760
1145
|
await dispatchModelReply({
|
|
761
1146
|
account: params.account,
|
|
762
1147
|
cfg: params.config,
|
|
@@ -771,7 +1156,7 @@ async function handleClickClackInbound(params) {
|
|
|
771
1156
|
let activity;
|
|
772
1157
|
if (params.account.agentActivity && (message.channel_id || message.direct_conversation_id)) activity = createClickClackActivityPublisher({
|
|
773
1158
|
client: createClickClackClient({
|
|
774
|
-
baseUrl: params.account.
|
|
1159
|
+
baseUrl: params.account.apiEndpoint,
|
|
775
1160
|
token: params.account.token,
|
|
776
1161
|
correlationId: params.correlationId
|
|
777
1162
|
}),
|
|
@@ -785,46 +1170,63 @@ async function handleClickClackInbound(params) {
|
|
|
785
1170
|
}
|
|
786
1171
|
});
|
|
787
1172
|
const senderName = message.author?.display_name || message.author_id;
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
})
|
|
792
|
-
const body = runtime.channel.reply.formatAgentEnvelope({
|
|
1173
|
+
const body = createChannelInboundEnvelopeBuilder({
|
|
1174
|
+
cfg: params.config,
|
|
1175
|
+
route
|
|
1176
|
+
})({
|
|
793
1177
|
channel: "ClickClack",
|
|
794
1178
|
from: senderName,
|
|
795
1179
|
timestamp: new Date(message.created_at),
|
|
796
|
-
previousTimestamp,
|
|
797
|
-
envelope: runtime.channel.reply.resolveEnvelopeFormatOptions(params.config),
|
|
798
1180
|
body: message.body
|
|
799
1181
|
});
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1182
|
+
const ctxPayload = runtime.channel.inbound.buildContext({
|
|
1183
|
+
channel: CHANNEL_ID$1,
|
|
1184
|
+
accountId: route.accountId ?? params.account.accountId,
|
|
1185
|
+
messageId: message.id,
|
|
1186
|
+
messageIdFull: message.id,
|
|
1187
|
+
timestamp: new Date(message.created_at).getTime(),
|
|
1188
|
+
from: target,
|
|
1189
|
+
sender: {
|
|
1190
|
+
id: message.author_id,
|
|
1191
|
+
name: senderName
|
|
1192
|
+
},
|
|
1193
|
+
conversation: {
|
|
1194
|
+
kind: isDirect ? "direct" : "group",
|
|
1195
|
+
id: conversationId,
|
|
1196
|
+
label: isDirect ? senderName : message.channel_id,
|
|
1197
|
+
threadId: message.parent_message_id ? message.thread_root_id : void 0,
|
|
1198
|
+
nativeChannelId: conversationId
|
|
1199
|
+
},
|
|
1200
|
+
route: {
|
|
1201
|
+
agentId: route.agentId,
|
|
1202
|
+
dmScope: route.dmScope,
|
|
1203
|
+
accountId: route.accountId,
|
|
1204
|
+
routeSessionKey: route.sessionKey
|
|
1205
|
+
},
|
|
1206
|
+
reply: {
|
|
1207
|
+
to: target,
|
|
1208
|
+
originatingTo: target,
|
|
1209
|
+
replyToId: message.id,
|
|
1210
|
+
messageThreadId: message.parent_message_id ? message.thread_root_id : void 0,
|
|
1211
|
+
threadParentId: message.parent_message_id ? message.thread_root_id : void 0
|
|
1212
|
+
},
|
|
1213
|
+
message: {
|
|
1214
|
+
body,
|
|
1215
|
+
bodyForAgent: message.body,
|
|
1216
|
+
rawBody: message.body,
|
|
1217
|
+
commandBody: message.body
|
|
1218
|
+
},
|
|
1219
|
+
access: {
|
|
1220
|
+
commands: { authorized: access.commandAuthorized },
|
|
1221
|
+
mentions: {
|
|
1222
|
+
canDetectMention: !isDirect,
|
|
1223
|
+
wasMentioned: !isDirect
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
extra: {
|
|
1227
|
+
GroupChannel: message.channel_id,
|
|
1228
|
+
...discussionRoute ? { GroupSystemPrompt: discussionRoute.systemPrompt } : {}
|
|
1229
|
+
}
|
|
828
1230
|
});
|
|
829
1231
|
const runId = resolveClickClackAgentRunId(message.id);
|
|
830
1232
|
const activityReplyOptions = activity ? {
|
|
@@ -840,16 +1242,16 @@ async function handleClickClackInbound(params) {
|
|
|
840
1242
|
suppressDefaultToolProgressMessages: true,
|
|
841
1243
|
allowProgressCallbacksWhenSourceDeliverySuppressed: true
|
|
842
1244
|
} : void 0;
|
|
843
|
-
const dispatchPromise = runtime.channel.inbound.
|
|
1245
|
+
const dispatchPromise = runtime.channel.inbound.dispatch({
|
|
844
1246
|
cfg: params.config,
|
|
845
1247
|
channel: CHANNEL_ID$1,
|
|
846
1248
|
accountId: params.account.accountId,
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1249
|
+
route: {
|
|
1250
|
+
agentId: route.agentId,
|
|
1251
|
+
dmScope: route.dmScope,
|
|
1252
|
+
sessionKey: route.sessionKey
|
|
1253
|
+
},
|
|
850
1254
|
ctxPayload,
|
|
851
|
-
recordInboundSession: runtime.channel.session.recordInboundSession,
|
|
852
|
-
dispatchReplyWithBufferedBlockDispatcher: runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
853
1255
|
toolsAllow: params.account.toolsAllow,
|
|
854
1256
|
replyOptions: runId || activityReplyOptions ? {
|
|
855
1257
|
...runId ? { runId } : {},
|
|
@@ -943,7 +1345,7 @@ async function processEvent(params) {
|
|
|
943
1345
|
const correlationId = eventCorrelationId(params.event);
|
|
944
1346
|
const message = await resolveEventMessage({
|
|
945
1347
|
client: correlationId ? createClickClackClient({
|
|
946
|
-
baseUrl: params.account.
|
|
1348
|
+
baseUrl: params.account.apiEndpoint,
|
|
947
1349
|
token: params.account.token,
|
|
948
1350
|
correlationId
|
|
949
1351
|
}) : params.client,
|
|
@@ -989,7 +1391,7 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
989
1391
|
});
|
|
990
1392
|
if (!configuredAccount.configured) throw new Error(`ClickClack is not configured for account "${configuredAccount.accountId}"`);
|
|
991
1393
|
const client = createClickClackClient({
|
|
992
|
-
baseUrl: configuredAccount.
|
|
1394
|
+
baseUrl: configuredAccount.apiEndpoint,
|
|
993
1395
|
token: configuredAccount.token
|
|
994
1396
|
});
|
|
995
1397
|
const workspaceId = await resolveWorkspaceId(client, configuredAccount.workspace);
|
|
@@ -1211,6 +1613,7 @@ const clickClackPlugin = createChatChannelPlugin({
|
|
|
1211
1613
|
configSchema: clickClackConfigSchema,
|
|
1212
1614
|
config: clickClackConfigAdapter,
|
|
1213
1615
|
setup: clickClackSetupAdapter,
|
|
1616
|
+
setupContract: clickClackSetupContract,
|
|
1214
1617
|
setupWizard: clickClackSetupWizard,
|
|
1215
1618
|
secrets: {
|
|
1216
1619
|
secretTargetRegistryEntries,
|
|
@@ -1316,4 +1719,4 @@ const clickClackPlugin = createChatChannelPlugin({
|
|
|
1316
1719
|
}
|
|
1317
1720
|
});
|
|
1318
1721
|
//#endregion
|
|
1319
|
-
export { setClickClackRuntime as a,
|
|
1722
|
+
export { reserveDiscussionBindingGeneration as C, recordPendingDiscussionOpen as S, setClickClackRuntime as T, isDiscussionSessionKey as _, isClickClackDiscussionChannelRevoked as a, clearDiscussionBindingGeneration as b, discussionAccounts as c, bindingMatchesActiveSessionIncarnation as d, bindingMatchesSessionIncarnation as f, fallbackDiscussionLabel as g, discussionExternalRef as h, clearClickClackDiscussionChannelRevoked as i, normalizedServerBaseUrl as l, discussionCredentialFingerprint as m, buildClickClackTarget as n, markClickClackDiscussionChannelIdentityRevoked as o, getClickClackDiscussionBindingStore as p, parseClickClackTarget as r, markClickClackDiscussionChannelRevoked as s, clickClackPlugin as t, resolveDiscussionBindingAccount as u, resolveDiscussionLabel as v, getClickClackRuntime as w, listPendingDiscussionOpens as x, slugifyDiscussionLabel as y };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as clickClackPlugin } from "./channel-
|
|
1
|
+
import { t as clickClackPlugin } from "./channel-BDq0KWDB.js";
|
|
2
2
|
export { clickClackPlugin };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor";
|
|
2
|
+
//#region extensions/clickclack/src/doctor-contract.ts
|
|
3
|
+
function stripTimeoutSeconds(value) {
|
|
4
|
+
const record = asObjectRecord(value);
|
|
5
|
+
if (!record) return {
|
|
6
|
+
value,
|
|
7
|
+
changed: false
|
|
8
|
+
};
|
|
9
|
+
let changed = false;
|
|
10
|
+
const next = {};
|
|
11
|
+
for (const [key, child] of Object.entries(record)) {
|
|
12
|
+
if (key === "timeoutSeconds") {
|
|
13
|
+
changed = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const stripped = stripTimeoutSeconds(child);
|
|
17
|
+
changed = changed || stripped.changed;
|
|
18
|
+
next[key] = stripped.value;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
value: changed ? next : value,
|
|
22
|
+
changed
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function normalizeCompatibilityConfig({ cfg }) {
|
|
26
|
+
const rawEntry = asObjectRecord(cfg.channels?.clickclack);
|
|
27
|
+
if (!rawEntry) return {
|
|
28
|
+
config: cfg,
|
|
29
|
+
changes: []
|
|
30
|
+
};
|
|
31
|
+
const stripped = stripTimeoutSeconds(rawEntry);
|
|
32
|
+
if (!stripped.changed) return {
|
|
33
|
+
config: cfg,
|
|
34
|
+
changes: []
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
config: {
|
|
38
|
+
...cfg,
|
|
39
|
+
channels: {
|
|
40
|
+
...cfg.channels,
|
|
41
|
+
clickclack: stripped.value
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
changes: ["Removed retired ClickClack timeout tuning knobs."]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { normalizeCompatibilityConfig };
|