@decentchat/decentchat-plugin 0.1.9

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.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * polyfill.ts — install node-datachannel globals so PeerJS works in Node.js
3
+ *
4
+ * This file SELF-INSTALLS as a side effect when imported.
5
+ * Import it first so RTCPeerConnection is set before PeerJS loads.
6
+ *
7
+ * Under jiti (OpenClaw's TypeScript loader), all static imports are hoisted
8
+ * to require() calls in order — so the FIRST import wins. Keep this as the
9
+ * very first import in DecentChatNodePeer.ts.
10
+ */
11
+
12
+ if (typeof RTCPeerConnection === 'undefined') {
13
+ try {
14
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
15
+ const dc = require('node-datachannel/polyfill') as Record<string, unknown>;
16
+
17
+ const globals: Record<string, unknown> = {
18
+ RTCPeerConnection: dc['RTCPeerConnection'],
19
+ RTCIceCandidate: dc['RTCIceCandidate'],
20
+ RTCSessionDescription: dc['RTCSessionDescription'],
21
+ RTCDataChannel: dc['RTCDataChannel'],
22
+ RTCDataChannelEvent: dc['RTCDataChannelEvent'],
23
+ RTCIceTransport: dc['RTCIceTransport'],
24
+ RTCPeerConnectionIceEvent: dc['RTCPeerConnectionIceEvent'],
25
+ MediaStream: dc['MediaStream'],
26
+ };
27
+
28
+ for (const [key, val] of Object.entries(globals)) {
29
+ if (val !== undefined) {
30
+ (globalThis as Record<string, unknown>)[key] = val;
31
+ }
32
+ }
33
+ } catch (e) {
34
+ throw new Error(
35
+ `node-datachannel not available. Run: npm install node-datachannel\nOriginal: ${e}`,
36
+ );
37
+ }
38
+ }
39
+
40
+ // Legacy export for backward compatibility
41
+ export function installWebRTCPolyfill(): void {
42
+ // No-op: installation happens at module load time (side effect above)
43
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared registry for active DecentChatNodePeer instances, keyed by account id.
3
+ * Allows channel.ts adapters to deliver outbound messages and directory lookups
4
+ * via the correct P2P peer when multiple DecentChat accounts are running.
5
+ */
6
+
7
+ import type { DecentChatNodePeer } from "./peer/DecentChatNodePeer.js";
8
+
9
+ const DEFAULT_ACCOUNT_ID = "default";
10
+
11
+ const activePeers = new Map<string, InstanceType<typeof DecentChatNodePeer>>();
12
+
13
+ export function setActivePeer(
14
+ peer: InstanceType<typeof DecentChatNodePeer> | null,
15
+ accountId: string = DEFAULT_ACCOUNT_ID,
16
+ ): void {
17
+ const key = accountId?.trim() || DEFAULT_ACCOUNT_ID;
18
+ if (peer) {
19
+ activePeers.set(key, peer);
20
+ } else {
21
+ activePeers.delete(key);
22
+ }
23
+ }
24
+
25
+ export function getActivePeer(accountId: string = DEFAULT_ACCOUNT_ID): InstanceType<typeof DecentChatNodePeer> | null {
26
+ const key = accountId?.trim() || DEFAULT_ACCOUNT_ID;
27
+ return activePeers.get(key) ?? null;
28
+ }
29
+
30
+ export function listActivePeerAccountIds(): string[] {
31
+ return Array.from(activePeers.keys()).sort((a, b) => a.localeCompare(b));
32
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk";
2
+
3
+ let runtime: PluginRuntime | null = null;
4
+
5
+ const bootstrapInFlight = new Map<string, Promise<void>>();
6
+ const bootstrapCompleted = new Set<string>();
7
+
8
+ export function setDecentChatRuntime(next: PluginRuntime): void {
9
+ runtime = next;
10
+ }
11
+
12
+ export function getDecentChatRuntime(): PluginRuntime {
13
+ if (!runtime) {
14
+ throw new Error("DecentChat runtime not initialized");
15
+ }
16
+ return runtime;
17
+ }
18
+
19
+ export function buildDecentChatRuntimeBootstrapKey(manifestPath: string, scope?: string): string {
20
+ const normalizedScope = scope?.trim();
21
+ if (normalizedScope) {
22
+ return `runtime:${manifestPath}:${normalizedScope}`;
23
+ }
24
+ return `runtime:${manifestPath}`;
25
+ }
26
+
27
+ export async function runDecentChatBootstrapOnce(key: string, task: () => Promise<void>): Promise<void> {
28
+ if (bootstrapCompleted.has(key)) return;
29
+
30
+ const inFlight = bootstrapInFlight.get(key);
31
+ if (inFlight) {
32
+ await inFlight;
33
+ return;
34
+ }
35
+
36
+ const run = (async () => {
37
+ try {
38
+ await task();
39
+ bootstrapCompleted.add(key);
40
+ } finally {
41
+ bootstrapInFlight.delete(key);
42
+ }
43
+ })();
44
+
45
+ bootstrapInFlight.set(key, run);
46
+ await run;
47
+ }
48
+
49
+ export function resetDecentChatRuntimeBootstrapStateForTests(): void {
50
+ bootstrapInFlight.clear();
51
+ bootstrapCompleted.clear();
52
+ }
53
+
54
+ /**
55
+ * Invalidate a specific bootstrap key so the next `runDecentChatBootstrapOnce`
56
+ * call with the same key will re-run the task. Used during gateway restarts
57
+ * to ensure bootstrap logic (workspace creation, invite joins, etc.) is not
58
+ * permanently skipped when the gateway recycles within the same process.
59
+ */
60
+ export function invalidateDecentChatBootstrapKey(key: string): void {
61
+ bootstrapCompleted.delete(key);
62
+ bootstrapInFlight.delete(key);
63
+ }
package/src/types.ts ADDED
@@ -0,0 +1,136 @@
1
+
2
+ export type OpenClawAgentListEntryConfig = {
3
+ id?: string;
4
+ workspace?: string;
5
+ [key: string]: unknown;
6
+ };
7
+
8
+ export type OpenClawRouteBindingConfig = {
9
+ type?: string;
10
+ agentId?: string;
11
+ match?: {
12
+ channel?: string;
13
+ accountId?: string;
14
+ };
15
+ [key: string]: unknown;
16
+ };
17
+
18
+ export type OpenClawConfigShape = {
19
+ channels?: Record<string, unknown>;
20
+ agents?: {
21
+ list?: OpenClawAgentListEntryConfig[];
22
+ [key: string]: unknown;
23
+ };
24
+ bindings?: OpenClawRouteBindingConfig[];
25
+ [key: string]: unknown;
26
+ };
27
+
28
+ export type DecentChatCompanySimBootstrapConfig = {
29
+ enabled?: boolean;
30
+ mode?: 'runtime' | 'off';
31
+ manifestPath?: string;
32
+ targetWorkspaceId?: string;
33
+ targetInviteCode?: string;
34
+ };
35
+
36
+ export type DecentChatCompanySimConfig = {
37
+ enabled?: boolean;
38
+ manifestPath?: string;
39
+ companyId?: string;
40
+ employeeId?: string;
41
+ roleFilesDir?: string;
42
+ silentChannelIds?: string[];
43
+ };
44
+
45
+ export type DecentChatChannelConfig = {
46
+ enabled?: boolean;
47
+ dmPolicy?: string;
48
+ seedPhrase?: string;
49
+ signalingServer?: string;
50
+ invites?: string[];
51
+ alias?: string;
52
+ dataDir?: string;
53
+ channels?: Record<string, { requireMention?: boolean }>;
54
+ streamEnabled?: boolean;
55
+ replyToMode?: "off" | "first" | "all";
56
+ replyToModeByChatType?: {
57
+ direct?: "off" | "first" | "all";
58
+ group?: "off" | "first" | "all";
59
+ channel?: "off" | "first" | "all";
60
+ };
61
+ thread?: {
62
+ historyScope?: "thread" | "channel";
63
+ inheritParent?: boolean;
64
+ initialHistoryLimit?: number;
65
+ };
66
+ huddle?: {
67
+ enabled?: boolean;
68
+ autoJoin?: boolean;
69
+ sttEngine?: 'whisper-cpp' | 'whisper-python' | 'openai' | 'groq';
70
+ whisperModel?: string;
71
+ sttLanguage?: string;
72
+ sttApiKey?: string;
73
+ ttsVoice?: string;
74
+ vadSilenceMs?: number;
75
+ vadThreshold?: number;
76
+ };
77
+ companySim?: DecentChatCompanySimConfig;
78
+ companySimBootstrap?: DecentChatCompanySimBootstrapConfig;
79
+ companySimBootstrapEnabled?: boolean;
80
+ companySimBootstrapMode?: 'runtime' | 'off';
81
+ companySimBootstrapManifestPath?: string;
82
+ companySimBootstrapTargetWorkspaceId?: string;
83
+ companySimBootstrapTargetInviteCode?: string;
84
+ defaultAccount?: string;
85
+ accounts?: Record<string, Omit<DecentChatChannelConfig, 'accounts'>>;
86
+ };
87
+
88
+ export type ResolvedDecentChatAccount = {
89
+ accountId: string;
90
+ enabled: boolean;
91
+ dmPolicy: string;
92
+ configured: boolean;
93
+ seedPhrase?: string;
94
+ signalingServer?: string;
95
+ invites: string[];
96
+ alias: string;
97
+ dataDir?: string;
98
+ streamEnabled: boolean;
99
+ replyToMode: "off" | "first" | "all";
100
+ replyToModeByChatType: {
101
+ direct?: "off" | "first" | "all";
102
+ group?: "off" | "first" | "all";
103
+ channel?: "off" | "first" | "all";
104
+ };
105
+ thread: {
106
+ historyScope: "thread" | "channel";
107
+ inheritParent: boolean;
108
+ initialHistoryLimit: number;
109
+ };
110
+ huddle?: {
111
+ enabled?: boolean;
112
+ autoJoin?: boolean;
113
+ sttEngine?: 'whisper-cpp' | 'whisper-python' | 'openai' | 'groq';
114
+ whisperModel?: string;
115
+ sttLanguage?: string;
116
+ sttApiKey?: string;
117
+ ttsVoice?: string;
118
+ vadSilenceMs?: number;
119
+ vadThreshold?: number;
120
+ };
121
+ companySim?: {
122
+ enabled: boolean;
123
+ manifestPath?: string;
124
+ companyId?: string;
125
+ employeeId?: string;
126
+ roleFilesDir?: string;
127
+ silentChannelIds?: string[];
128
+ };
129
+ companySimBootstrap?: {
130
+ enabled: boolean;
131
+ mode: 'runtime' | 'off';
132
+ manifestPath?: string;
133
+ targetWorkspaceId?: string;
134
+ targetInviteCode?: string;
135
+ };
136
+ };