@insta-dev01/intclaw 1.0.11 → 1.1.0
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/LICENSE +1 -1
- package/README.en.md +424 -0
- package/README.md +365 -164
- package/index.ts +28 -0
- package/openclaw.plugin.json +12 -41
- package/package.json +69 -40
- package/src/channel.ts +557 -0
- package/src/config/accounts.ts +230 -0
- package/src/config/schema.ts +144 -0
- package/src/core/connection.ts +733 -0
- package/src/core/message-handler.ts +1268 -0
- package/src/core/provider.ts +106 -0
- package/src/core/state.ts +54 -0
- package/src/directory.ts +95 -0
- package/src/gateway-methods.ts +237 -0
- package/src/onboarding.ts +387 -0
- package/src/policy.ts +19 -0
- package/src/probe.ts +213 -0
- package/src/reply-dispatcher.ts +674 -0
- package/src/runtime.ts +7 -0
- package/src/sdk/helpers.ts +317 -0
- package/src/sdk/types.ts +515 -0
- package/src/secret-input.ts +19 -0
- package/src/services/media/audio.ts +54 -0
- package/src/services/media/chunk-upload.ts +293 -0
- package/src/services/media/common.ts +154 -0
- package/src/services/media/file.ts +70 -0
- package/src/services/media/image.ts +67 -0
- package/src/services/media/index.ts +10 -0
- package/src/services/media/video.ts +162 -0
- package/src/services/media.ts +1134 -0
- package/src/services/messaging/index.ts +16 -0
- package/src/services/messaging/send.ts +137 -0
- package/src/services/messaging.ts +800 -0
- package/src/targets.ts +45 -0
- package/src/types/index.ts +52 -0
- package/src/utils/agent.ts +63 -0
- package/src/utils/async.ts +51 -0
- package/src/utils/constants.ts +9 -0
- package/src/utils/http-client.ts +84 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +78 -0
- package/src/utils/session.ts +118 -0
- package/src/utils/token.ts +94 -0
- package/src/utils/utils-legacy.ts +506 -0
- package/.env.example +0 -11
- package/skills/intclaw_matrix/SKILL.md +0 -20
- package/src/channel/intclaw_channel.js +0 -155
- package/src/index.js +0 -23
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId , normalizeResolvedSecretInputString, normalizeSecretInputString } from "../sdk/helpers.ts";
|
|
2
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
3
|
+
import type {
|
|
4
|
+
IntclawConfig,
|
|
5
|
+
IntclawAccountConfig,
|
|
6
|
+
IntclawDefaultAccountSelectionSource,
|
|
7
|
+
ResolvedIntclawAccount,
|
|
8
|
+
} from "../types/index.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all configured account IDs from the accounts field.
|
|
12
|
+
*/
|
|
13
|
+
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
|
14
|
+
const accounts = (cfg.channels?.["intclaw-connector"] as IntclawConfig)?.accounts;
|
|
15
|
+
if (!accounts || typeof accounts !== "object") {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return Object.keys(accounts).filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List all IntClaw account IDs.
|
|
23
|
+
* If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
|
|
24
|
+
*/
|
|
25
|
+
export function listIntclawAccountIds(cfg: ClawdbotConfig): string[] {
|
|
26
|
+
const ids = listConfiguredAccountIds(cfg);
|
|
27
|
+
if (ids.length === 0) {
|
|
28
|
+
// Backward compatibility: no accounts configured, use default
|
|
29
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
30
|
+
}
|
|
31
|
+
return [...ids].toSorted((a, b) => a.localeCompare(b));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the default account selection and its source.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveDefaultIntclawAccountSelection(cfg: ClawdbotConfig): {
|
|
38
|
+
accountId: string;
|
|
39
|
+
source: IntclawDefaultAccountSelectionSource;
|
|
40
|
+
} {
|
|
41
|
+
const preferredRaw = (cfg.channels?.["intclaw-connector"] as IntclawConfig | undefined)?.defaultAccount?.trim();
|
|
42
|
+
const preferred = preferredRaw ? normalizeAccountId(preferredRaw) : undefined;
|
|
43
|
+
if (preferred) {
|
|
44
|
+
return {
|
|
45
|
+
accountId: preferred,
|
|
46
|
+
source: "explicit-default",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const ids = listIntclawAccountIds(cfg);
|
|
50
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
51
|
+
return {
|
|
52
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
53
|
+
source: "mapped-default",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
accountId: ids[0] ?? DEFAULT_ACCOUNT_ID,
|
|
58
|
+
source: "fallback",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the default account ID.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveDefaultIntclawAccountId(cfg: ClawdbotConfig): string {
|
|
66
|
+
return resolveDefaultIntclawAccountSelection(cfg).accountId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the raw account-specific config.
|
|
71
|
+
*/
|
|
72
|
+
function resolveAccountConfig(
|
|
73
|
+
cfg: ClawdbotConfig,
|
|
74
|
+
accountId: string,
|
|
75
|
+
): IntclawAccountConfig | undefined {
|
|
76
|
+
const accounts = (cfg.channels?.["intclaw-connector"] as IntclawConfig)?.accounts;
|
|
77
|
+
if (!accounts || typeof accounts !== "object") {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return accounts[accountId];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Merge top-level config with account-specific config.
|
|
85
|
+
* Account-specific fields override top-level fields.
|
|
86
|
+
*/
|
|
87
|
+
function mergeIntclawAccountConfig(cfg: ClawdbotConfig, accountId: string): IntclawConfig {
|
|
88
|
+
const intclawCfg = cfg.channels?.["intclaw-connector"] as IntclawConfig | undefined;
|
|
89
|
+
|
|
90
|
+
// Extract base config (exclude accounts field to avoid recursion)
|
|
91
|
+
const { accounts: _ignored, defaultAccount: _ignoredDefaultAccount, ...base } = intclawCfg ?? {};
|
|
92
|
+
|
|
93
|
+
// Get account-specific overrides
|
|
94
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
95
|
+
|
|
96
|
+
// Merge: account config overrides base config
|
|
97
|
+
return { ...base, ...account } as IntclawConfig;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve IntClaw credentials from a config.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveIntclawCredentials(cfg?: IntclawConfig): {
|
|
104
|
+
clientId: string;
|
|
105
|
+
clientSecret: string;
|
|
106
|
+
} | null;
|
|
107
|
+
export function resolveIntclawCredentials(
|
|
108
|
+
cfg: IntclawConfig | undefined,
|
|
109
|
+
options: { allowUnresolvedSecretRef?: boolean },
|
|
110
|
+
): {
|
|
111
|
+
clientId: string;
|
|
112
|
+
clientSecret: string;
|
|
113
|
+
} | null;
|
|
114
|
+
export function resolveIntclawCredentials(
|
|
115
|
+
cfg?: IntclawConfig,
|
|
116
|
+
options?: { allowUnresolvedSecretRef?: boolean },
|
|
117
|
+
): {
|
|
118
|
+
clientId: string;
|
|
119
|
+
clientSecret: string;
|
|
120
|
+
} | null {
|
|
121
|
+
const normalizeString = (value: unknown): string | undefined => {
|
|
122
|
+
if (typeof value === "number") {
|
|
123
|
+
return String(value);
|
|
124
|
+
}
|
|
125
|
+
if (typeof value !== "string") {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const trimmed = value.trim();
|
|
129
|
+
return trimmed ? trimmed : undefined;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const resolveSecretLike = (value: unknown, path: string): string | undefined => {
|
|
133
|
+
// Missing credential: treat as not configured (no exception).
|
|
134
|
+
// This path is used in non-onboarding contexts (e.g. channel listing/status),
|
|
135
|
+
// so we must not throw when credentials are absent.
|
|
136
|
+
if (value === undefined || value === null) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const asString = normalizeString(value);
|
|
141
|
+
if (asString) {
|
|
142
|
+
return asString;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// In relaxed/onboarding paths only: allow direct env SecretRef reads for UX.
|
|
146
|
+
// Default resolution path must preserve unresolved-ref diagnostics/policy semantics.
|
|
147
|
+
if (options?.allowUnresolvedSecretRef && typeof value === "object" && value !== null) {
|
|
148
|
+
const rec = value as Record<string, unknown>;
|
|
149
|
+
const source = normalizeString(rec.source)?.toLowerCase();
|
|
150
|
+
const id = normalizeString(rec.id);
|
|
151
|
+
if (source === "env" && id) {
|
|
152
|
+
const envValue = normalizeString(process.env[id]);
|
|
153
|
+
if (envValue) {
|
|
154
|
+
return envValue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options?.allowUnresolvedSecretRef) {
|
|
160
|
+
return normalizeSecretInputString(value);
|
|
161
|
+
}
|
|
162
|
+
return normalizeResolvedSecretInputString({ value, path });
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const clientId = resolveSecretLike(cfg?.clientId, "channels.intclaw-connector.clientId");
|
|
166
|
+
const clientSecret = resolveSecretLike(cfg?.clientSecret, "channels.intclaw-connector.clientSecret");
|
|
167
|
+
|
|
168
|
+
if (!clientId || !clientSecret) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
clientId,
|
|
173
|
+
clientSecret,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a complete IntClaw account with merged config.
|
|
179
|
+
*/
|
|
180
|
+
export function resolveIntclawAccount(params: {
|
|
181
|
+
cfg: ClawdbotConfig;
|
|
182
|
+
accountId?: string | null;
|
|
183
|
+
}): ResolvedIntclawAccount {
|
|
184
|
+
const hasExplicitAccountId =
|
|
185
|
+
typeof params.accountId === "string" && params.accountId.trim() !== "";
|
|
186
|
+
const defaultSelection = hasExplicitAccountId
|
|
187
|
+
? null
|
|
188
|
+
: resolveDefaultIntclawAccountSelection(params.cfg);
|
|
189
|
+
const accountId = hasExplicitAccountId
|
|
190
|
+
? normalizeAccountId(params.accountId ?? "")
|
|
191
|
+
: (defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
192
|
+
const selectionSource = hasExplicitAccountId
|
|
193
|
+
? "explicit"
|
|
194
|
+
: (defaultSelection?.source ?? "fallback");
|
|
195
|
+
const intclawCfg = params.cfg.channels?.["intclaw-connector"] as IntclawConfig | undefined;
|
|
196
|
+
|
|
197
|
+
// Base enabled state (top-level)
|
|
198
|
+
const baseEnabled = intclawCfg?.enabled !== false;
|
|
199
|
+
|
|
200
|
+
// Merge configs
|
|
201
|
+
const merged = mergeIntclawAccountConfig(params.cfg, accountId);
|
|
202
|
+
|
|
203
|
+
// Account-level enabled state
|
|
204
|
+
const accountEnabled = merged.enabled !== false;
|
|
205
|
+
const enabled = baseEnabled && accountEnabled;
|
|
206
|
+
|
|
207
|
+
// Resolve credentials from merged config
|
|
208
|
+
const creds = resolveIntclawCredentials(merged);
|
|
209
|
+
const accountName = (merged as IntclawAccountConfig).name;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
accountId,
|
|
213
|
+
selectionSource,
|
|
214
|
+
enabled,
|
|
215
|
+
configured: Boolean(creds),
|
|
216
|
+
name: typeof accountName === "string" ? accountName.trim() || undefined : undefined,
|
|
217
|
+
clientId: creds?.clientId,
|
|
218
|
+
clientSecret: creds?.clientSecret,
|
|
219
|
+
config: merged,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* List all enabled and configured accounts.
|
|
225
|
+
*/
|
|
226
|
+
export function listEnabledIntclawAccounts(cfg: ClawdbotConfig): ResolvedIntclawAccount[] {
|
|
227
|
+
return listIntclawAccountIds(cfg)
|
|
228
|
+
.map((accountId) => resolveIntclawAccount({ cfg, accountId }))
|
|
229
|
+
.filter((account) => account.enabled && account.configured);
|
|
230
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { normalizeAccountId } from "../sdk/helpers.ts";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export { z };
|
|
4
|
+
import { buildSecretInputSchema, hasConfiguredSecretInput } from "../secret-input.ts";
|
|
5
|
+
|
|
6
|
+
const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
|
|
7
|
+
const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
|
|
8
|
+
|
|
9
|
+
const ToolPolicySchema = z
|
|
10
|
+
.object({
|
|
11
|
+
allow: z.array(z.string()).optional(),
|
|
12
|
+
deny: z.array(z.string()).optional(),
|
|
13
|
+
})
|
|
14
|
+
.strict()
|
|
15
|
+
.optional();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Group session scope for routing IntClaw group messages.
|
|
19
|
+
* - "group" (default): one session per group chat
|
|
20
|
+
* - "group_sender": one session per (group + sender)
|
|
21
|
+
*/
|
|
22
|
+
const GroupSessionScopeSchema = z
|
|
23
|
+
.enum(["group", "group_sender"])
|
|
24
|
+
.optional();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Intclaw tools configuration.
|
|
28
|
+
* Controls which tool categories are enabled.
|
|
29
|
+
*/
|
|
30
|
+
const IntclawToolsConfigSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
docs: z.boolean().optional(), // Document operations (default: true)
|
|
33
|
+
media: z.boolean().optional(), // Media upload operations (default: true)
|
|
34
|
+
})
|
|
35
|
+
.strict()
|
|
36
|
+
.optional();
|
|
37
|
+
|
|
38
|
+
export const IntclawGroupSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
requireMention: z.boolean().optional(),
|
|
41
|
+
tools: ToolPolicySchema,
|
|
42
|
+
enabled: z.boolean().optional(),
|
|
43
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
44
|
+
systemPrompt: z.string().optional(),
|
|
45
|
+
groupSessionScope: GroupSessionScopeSchema,
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
|
|
49
|
+
const IntclawSharedConfigShape = {
|
|
50
|
+
dmPolicy: DmPolicySchema.optional(),
|
|
51
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
52
|
+
groupPolicy: GroupPolicySchema.optional(),
|
|
53
|
+
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
54
|
+
requireMention: z.boolean().optional(),
|
|
55
|
+
groups: z.record(z.string(), IntclawGroupSchema.optional()).optional(),
|
|
56
|
+
historyLimit: z.number().int().min(0).optional(),
|
|
57
|
+
dmHistoryLimit: z.number().int().min(0).optional(),
|
|
58
|
+
textChunkLimit: z.number().int().positive().optional(),
|
|
59
|
+
mediaMaxMb: z.number().positive().optional(),
|
|
60
|
+
tools: IntclawToolsConfigSchema,
|
|
61
|
+
typingIndicator: z.boolean().optional(),
|
|
62
|
+
resolveSenderNames: z.boolean().optional(),
|
|
63
|
+
separateSessionByConversation: z.boolean().optional(),
|
|
64
|
+
sharedMemoryAcrossConversations: z.boolean().optional(),
|
|
65
|
+
groupSessionScope: GroupSessionScopeSchema,
|
|
66
|
+
asyncMode: z.boolean().optional(),
|
|
67
|
+
ackText: z.string().optional(),
|
|
68
|
+
endpoint: z.string().optional(), // DWClient gateway endpoint
|
|
69
|
+
debug: z.boolean().optional(), // DWClient debug mode
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Per-account configuration.
|
|
74
|
+
* All fields are optional - missing fields inherit from top-level config.
|
|
75
|
+
*/
|
|
76
|
+
export const IntclawAccountConfigSchema = z
|
|
77
|
+
.object({
|
|
78
|
+
enabled: z.boolean().optional(),
|
|
79
|
+
name: z.string().optional(), // Display name for this account
|
|
80
|
+
clientId: z.union([z.string(), z.number()]).optional(),
|
|
81
|
+
clientSecret: buildSecretInputSchema().optional(),
|
|
82
|
+
...IntclawSharedConfigShape,
|
|
83
|
+
})
|
|
84
|
+
.strict();
|
|
85
|
+
|
|
86
|
+
export const IntclawConfigSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
enabled: z.boolean().optional(),
|
|
89
|
+
defaultAccount: z.string().optional(),
|
|
90
|
+
// Top-level credentials (backward compatible for single-account mode)
|
|
91
|
+
clientId: z.union([z.string(), z.number()]).optional(),
|
|
92
|
+
clientSecret: buildSecretInputSchema().optional(),
|
|
93
|
+
enableMediaUpload: z.boolean().optional(),
|
|
94
|
+
systemPrompt: z.string().optional(),
|
|
95
|
+
...IntclawSharedConfigShape,
|
|
96
|
+
dmPolicy: DmPolicySchema.optional().default("open"),
|
|
97
|
+
groupPolicy: GroupPolicySchema.optional().default("open"),
|
|
98
|
+
requireMention: z.boolean().optional().default(true),
|
|
99
|
+
separateSessionByConversation: z.boolean().optional().default(true),
|
|
100
|
+
sharedMemoryAcrossConversations: z.boolean().optional().default(false),
|
|
101
|
+
groupSessionScope: GroupSessionScopeSchema.optional().default("group"),
|
|
102
|
+
// Multi-account configuration
|
|
103
|
+
accounts: z.record(z.string(), IntclawAccountConfigSchema.optional()).optional(),
|
|
104
|
+
})
|
|
105
|
+
.strict()
|
|
106
|
+
.superRefine((value, ctx) => {
|
|
107
|
+
const defaultAccount = value.defaultAccount?.trim();
|
|
108
|
+
if (defaultAccount && value.accounts && Object.keys(value.accounts).length > 0) {
|
|
109
|
+
const normalizedDefaultAccount = normalizeAccountId(defaultAccount);
|
|
110
|
+
if (!Object.prototype.hasOwnProperty.call(value.accounts, normalizedDefaultAccount)) {
|
|
111
|
+
ctx.addIssue({
|
|
112
|
+
code: z.ZodIssueCode.custom,
|
|
113
|
+
path: ["defaultAccount"],
|
|
114
|
+
message: `channels.intclaw-connector.defaultAccount="${defaultAccount}" does not match a configured account key`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate dmPolicy and allowFrom consistency
|
|
120
|
+
if (value.dmPolicy === "allowlist") {
|
|
121
|
+
const allowFrom = value.allowFrom ?? [];
|
|
122
|
+
if (allowFrom.length === 0) {
|
|
123
|
+
ctx.addIssue({
|
|
124
|
+
code: z.ZodIssueCode.custom,
|
|
125
|
+
path: ["allowFrom"],
|
|
126
|
+
message:
|
|
127
|
+
'channels.intclaw-connector.dmPolicy="allowlist" requires channels.intclaw-connector.allowFrom to contain at least one entry',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate groupPolicy and groupAllowFrom consistency
|
|
133
|
+
if (value.groupPolicy === "allowlist") {
|
|
134
|
+
const groupAllowFrom = value.groupAllowFrom ?? [];
|
|
135
|
+
if (groupAllowFrom.length === 0) {
|
|
136
|
+
ctx.addIssue({
|
|
137
|
+
code: z.ZodIssueCode.custom,
|
|
138
|
+
path: ["groupAllowFrom"],
|
|
139
|
+
message:
|
|
140
|
+
'channels.intclaw-connector.groupPolicy="allowlist" requires channels.intclaw-connector.groupAllowFrom to contain at least one entry',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|