openclaw-extension-typex 1.0.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/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @openclaw/typex
2
+
3
+ TypeX channel plugin for OpenClaw.
4
+
5
+ This plugin is modeled after the Feishu channel plugin in `extensions/feishu/`, so you can
6
+ use that implementation as a reference when wiring up the real TypeX provider.
7
+
8
+ ## Install (local checkout)
9
+
10
+ ```bash
11
+ openclaw plugins install ./extensions/typex
12
+ ```
13
+
14
+ ## Install (npm)
15
+
16
+ ```bash
17
+ openclaw plugins install @openclaw/typex
18
+ ```
19
+
20
+ Onboarding: select TypeX and confirm the install prompt to fetch the plugin automatically.
21
+
22
+ ## Config
23
+
24
+ ```json5
25
+ {
26
+ channels: {
27
+ typex: {
28
+ accounts: {
29
+ default: {
30
+ appId: "app_xxx",
31
+ appSecret: "xxx",
32
+ enabled: true,
33
+ },
34
+ },
35
+ dmPolicy: "pairing",
36
+ groupPolicy: "open",
37
+ blockStreaming: true,
38
+ },
39
+ },
40
+ }
41
+ ```
42
+
43
+ Once the actual TypeX provider is implemented in the core `openclaw` repo, you can extend
44
+ this plugin to wire outbound messaging and gateway/runtime logic, mirroring the Feishu
45
+ implementation.
46
+
47
+ ## Docs
48
+
49
+ https://docs.openclaw.ai/channels/typex
@@ -0,0 +1,16 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk";
2
+ import type { TypeXAccountConfig } from "../types.js";
3
+ export type TypeXTokenSource = "config" | "file" | "env" | "none";
4
+ export type ResolvedTypeXAccount = {
5
+ accountId: string;
6
+ config: TypeXAccountConfig;
7
+ tokenSource: TypeXTokenSource;
8
+ name?: string;
9
+ enabled: boolean;
10
+ };
11
+ export declare function listTypeXAccountIds(cfg: OpenClawConfig): string[];
12
+ export declare function resolveDefaultTypeXAccountId(cfg: OpenClawConfig): string;
13
+ export declare function resolveTypeXAccount(params: {
14
+ cfg: OpenClawConfig;
15
+ accountId?: string | null;
16
+ }): ResolvedTypeXAccount;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listTypeXAccountIds = listTypeXAccountIds;
7
+ exports.resolveDefaultTypeXAccountId = resolveDefaultTypeXAccountId;
8
+ exports.resolveTypeXAccount = resolveTypeXAccount;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const plugin_sdk_1 = require("openclaw/plugin-sdk");
11
+ function readFileIfExists(filePath) {
12
+ if (!filePath) {
13
+ return undefined;
14
+ }
15
+ try {
16
+ return node_fs_1.default.readFileSync(filePath, "utf-8").trim();
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ function resolveAccountConfig(cfg, accountId) {
23
+ const accounts = cfg.channels?.typex?.accounts;
24
+ if (!accounts || typeof accounts !== "object") {
25
+ return undefined;
26
+ }
27
+ const direct = accounts[accountId];
28
+ if (direct) {
29
+ return direct;
30
+ }
31
+ const normalized = (0, plugin_sdk_1.normalizeAccountId)(accountId);
32
+ const matchKey = Object.keys(accounts).find((key) => (0, plugin_sdk_1.normalizeAccountId)(key) === normalized);
33
+ return matchKey ? accounts[matchKey] : undefined;
34
+ }
35
+ function mergeTypeXAccountConfig(cfg, accountId) {
36
+ const { accounts: _ignored, ...base } = (cfg.channels?.typex ?? {});
37
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
38
+ return { ...base, ...account };
39
+ }
40
+ function resolveAppSecret(config) {
41
+ const direct = config?.appSecret?.trim();
42
+ if (direct) {
43
+ return { value: direct, source: "config" };
44
+ }
45
+ const fromFile = readFileIfExists(config?.appSecretFile);
46
+ if (fromFile) {
47
+ return { value: fromFile, source: "file" };
48
+ }
49
+ return {};
50
+ }
51
+ function listTypeXAccountIds(cfg) {
52
+ const typexCfg = cfg.channels?.typex;
53
+ const accounts = typexCfg?.accounts;
54
+ const ids = new Set();
55
+ const baseConfigured = Boolean(typexCfg?.appId?.trim() && (typexCfg?.appSecret?.trim() || Boolean(typexCfg?.appSecretFile)));
56
+ const envConfigured = Boolean(process.env.TYPEX_APP_ID?.trim() && process.env.TYPEX_APP_SECRET?.trim());
57
+ if (baseConfigured || envConfigured) {
58
+ ids.add(plugin_sdk_1.DEFAULT_ACCOUNT_ID);
59
+ }
60
+ if (accounts) {
61
+ for (const id of Object.keys(accounts)) {
62
+ ids.add((0, plugin_sdk_1.normalizeAccountId)(id));
63
+ }
64
+ }
65
+ return Array.from(ids);
66
+ }
67
+ function resolveDefaultTypeXAccountId(cfg) {
68
+ const ids = listTypeXAccountIds(cfg);
69
+ if (ids.includes(plugin_sdk_1.DEFAULT_ACCOUNT_ID)) {
70
+ return plugin_sdk_1.DEFAULT_ACCOUNT_ID;
71
+ }
72
+ return ids[0] ?? plugin_sdk_1.DEFAULT_ACCOUNT_ID;
73
+ }
74
+ function resolveTypeXAccount(params) {
75
+ const accountId = (0, plugin_sdk_1.normalizeAccountId)(params.accountId);
76
+ const baseEnabled = params.cfg.channels?.typex?.enabled !== false;
77
+ const merged = mergeTypeXAccountConfig(params.cfg, accountId);
78
+ const accountEnabled = merged.enabled !== false;
79
+ const enabled = baseEnabled && accountEnabled;
80
+ const allowEnv = accountId === plugin_sdk_1.DEFAULT_ACCOUNT_ID;
81
+ const envAppId = allowEnv ? process.env.TYPEX_APP_ID?.trim() : undefined;
82
+ const envAppSecret = allowEnv ? process.env.TYPEX_APP_SECRET?.trim() : undefined;
83
+ const appId = merged.appId?.trim() || envAppId || "";
84
+ const secretResolution = resolveAppSecret(merged);
85
+ const appSecret = secretResolution.value ?? envAppSecret ?? "";
86
+ let tokenSource = "none";
87
+ if (secretResolution.value) {
88
+ tokenSource = secretResolution.source ?? "config";
89
+ }
90
+ else if (envAppSecret) {
91
+ tokenSource = "env";
92
+ }
93
+ if (!appId || !appSecret) {
94
+ tokenSource = "none";
95
+ }
96
+ const config = {
97
+ ...merged,
98
+ appId,
99
+ appSecret,
100
+ };
101
+ const name = config.name?.trim() || config.botName?.trim() || undefined;
102
+ return {
103
+ accountId,
104
+ config,
105
+ tokenSource,
106
+ name,
107
+ enabled,
108
+ };
109
+ }
@@ -0,0 +1,14 @@
1
+ import { TypeXMessageEnum, type TypeXClientOptions } from "./types.js";
2
+ export declare class TypeXClient {
3
+ private options;
4
+ private accessToken?;
5
+ private userId?;
6
+ constructor(options: TypeXClientOptions);
7
+ getAccessToken(): Promise<string>;
8
+ getCurUserId(): Promise<string>;
9
+ fetchQrcodeUrl(): Promise<any>;
10
+ checkLoginStatus(qrcodeId: string): Promise<boolean>;
11
+ sendMessage(content: string | object, msgType?: TypeXMessageEnum): Promise<any>;
12
+ fetchMessages(pos: number): Promise<any>;
13
+ }
14
+ export declare function getTypeXClient(accountId?: string, manualOptions?: TypeXClientOptions): TypeXClient;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypeXClient = void 0;
4
+ exports.getTypeXClient = getTypeXClient;
5
+ const TYPEX_DOMAIN = "https://api-coco.typex.im";
6
+ // const TYPEX_DOMAIN = "https://api-tx.bossjob.net.cn";
7
+ let prompter;
8
+ class TypeXClient {
9
+ options;
10
+ accessToken;
11
+ userId;
12
+ constructor(options) {
13
+ this.options = options;
14
+ if (options.token) {
15
+ this.accessToken = options.token;
16
+ }
17
+ }
18
+ async getAccessToken() {
19
+ if (this.accessToken) {
20
+ return this.accessToken;
21
+ }
22
+ return "";
23
+ }
24
+ async getCurUserId() {
25
+ if (this.userId) {
26
+ return this.userId;
27
+ }
28
+ return "";
29
+ }
30
+ async fetchQrcodeUrl() {
31
+ try {
32
+ const qrResponse = await fetch(`${TYPEX_DOMAIN}/user/qrcode?login_type=open`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({}),
36
+ });
37
+ if (!qrResponse.ok) {
38
+ throw new Error(`Failed to get QR code: ${qrResponse.statusText}`);
39
+ }
40
+ const qrResult = await qrResponse.json();
41
+ if (qrResult.code !== 0 || !qrResult.data) {
42
+ throw new Error(`Failed to get QR code: ${qrResult.msg}`);
43
+ }
44
+ return qrResult.data;
45
+ }
46
+ catch (error) {
47
+ throw error;
48
+ }
49
+ }
50
+ async checkLoginStatus(qrcodeId) {
51
+ try {
52
+ const checkRes = await fetch(`${TYPEX_DOMAIN}/open/qrcode/check_auth`, {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ },
57
+ body: JSON.stringify({
58
+ qr_code_id: qrcodeId,
59
+ }),
60
+ });
61
+ const setCookieHeader = checkRes.headers.get("set-cookie");
62
+ if (setCookieHeader) {
63
+ const match = setCookieHeader.match(/(sessionid=[^;]+)/);
64
+ if (match && match[1]) {
65
+ this.accessToken = match[1];
66
+ }
67
+ }
68
+ const checkData = await checkRes.json();
69
+ if (checkData.code === 0) {
70
+ const { user_id } = checkData.data;
71
+ this.userId = user_id;
72
+ return true;
73
+ }
74
+ else if (checkData.code === 10001) {
75
+ return false;
76
+ }
77
+ else {
78
+ return false;
79
+ }
80
+ }
81
+ catch (error) {
82
+ throw error;
83
+ }
84
+ }
85
+ async sendMessage(content, msgType = 0) {
86
+ const token = this.accessToken;
87
+ if (!token) {
88
+ throw new Error("TypeXClient: Not authenticated.");
89
+ }
90
+ let finalContent = content;
91
+ if (typeof content === "object") {
92
+ try {
93
+ finalContent = JSON.stringify(content);
94
+ }
95
+ catch (e) {
96
+ if (e instanceof Error) {
97
+ prompter.note("Failed to stringify message content");
98
+ }
99
+ finalContent = String(content);
100
+ }
101
+ }
102
+ prompter.note(`TypeXClient sending message: content=${typeof finalContent === "string" ? finalContent : JSON.stringify(finalContent)}`);
103
+ try {
104
+ const url = `${TYPEX_DOMAIN}/open/claw/send_message`;
105
+ const response = await fetch(url, {
106
+ method: "POST",
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Cookie: token,
110
+ },
111
+ body: JSON.stringify({
112
+ content: {
113
+ text: finalContent,
114
+ },
115
+ msg_type: msgType,
116
+ }),
117
+ });
118
+ const resJson = await response.json();
119
+ if (resJson.code !== 0) {
120
+ throw new Error(`Send message failed: [${resJson.code}] ${resJson.message}`);
121
+ }
122
+ prompter.note("Message sent successfully", resJson.data);
123
+ return (resJson.data || {
124
+ message_id: `msg_${Date.now()}`,
125
+ });
126
+ }
127
+ catch (error) {
128
+ prompter.note(`Error sending message to TypeX API: ${error}`);
129
+ throw error;
130
+ }
131
+ }
132
+ async fetchMessages(pos) {
133
+ if (!this.accessToken) {
134
+ prompter.note("TypeXClient: No token, skipping fetch.");
135
+ return [];
136
+ }
137
+ try {
138
+ const url = `${TYPEX_DOMAIN}/open/claw/message`;
139
+ prompter.note(`Fetching messages from pos: ${pos}`);
140
+ const response = await fetch(url, {
141
+ method: "POST",
142
+ headers: {
143
+ Cookie: this.accessToken,
144
+ "Content-Type": "application/json",
145
+ },
146
+ body: JSON.stringify({ pos: pos }),
147
+ });
148
+ const resJson = await response.json();
149
+ if (resJson.code !== 0) {
150
+ prompter.note(`Fetch failed with code ${resJson.code}: ${resJson.message}`);
151
+ return [];
152
+ }
153
+ if (Array.isArray(resJson.data)) {
154
+ return resJson.data;
155
+ }
156
+ return [];
157
+ }
158
+ catch (e) {
159
+ prompter.note(`Fetch messages network error: ${e}`);
160
+ return [];
161
+ }
162
+ }
163
+ }
164
+ exports.TypeXClient = TypeXClient;
165
+ function getTypeXClient(accountId, manualOptions) {
166
+ const typexCfg = (manualOptions?.typexCfg ?? {});
167
+ const clawPrompter = manualOptions?.prompter;
168
+ if (clawPrompter) {
169
+ prompter = clawPrompter;
170
+ }
171
+ let token = manualOptions?.token;
172
+ if (accountId && typexCfg.accounts?.[accountId]) {
173
+ token = typexCfg.accounts[accountId].token;
174
+ }
175
+ if (!manualOptions?.skipConfigCheck) {
176
+ throw new Error("TypeX email not configured yet.");
177
+ }
178
+ return new TypeXClient({
179
+ token: token,
180
+ });
181
+ }
@@ -0,0 +1,25 @@
1
+ import type { OpenClawConfig, DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
2
+ import type { TypeXGroupConfig } from "../types";
3
+ export type ResolvedTypeXConfig = {
4
+ enabled: boolean;
5
+ dmPolicy: DmPolicy;
6
+ groupPolicy: GroupPolicy;
7
+ allowFrom: string[];
8
+ groupAllowFrom: string[];
9
+ historyLimit: number;
10
+ dmHistoryLimit: number;
11
+ textChunkLimit: number;
12
+ chunkMode: "length" | "newline";
13
+ blockStreaming: boolean;
14
+ streaming: boolean;
15
+ mediaMaxMb: number;
16
+ groups: Record<string, TypeXGroupConfig>;
17
+ };
18
+ /**
19
+ * Resolve effective TypeX configuration for an account.
20
+ * Account-level config overrides top-level typex config, which overrides channel defaults.
21
+ */
22
+ export declare function resolveTypeXConfig(params: {
23
+ cfg: OpenClawConfig;
24
+ accountId?: string;
25
+ }): ResolvedTypeXConfig;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTypeXConfig = resolveTypeXConfig;
4
+ const firstDefined = (...values) => {
5
+ for (const value of values) {
6
+ if (typeof value !== "undefined") {
7
+ return value;
8
+ }
9
+ }
10
+ return undefined;
11
+ };
12
+ /**
13
+ * Resolve effective TypeX configuration for an account.
14
+ * Account-level config overrides top-level typex config, which overrides channel defaults.
15
+ */
16
+ function resolveTypeXConfig(params) {
17
+ const { cfg, accountId } = params;
18
+ const typexCfg = cfg.channels?.typex;
19
+ const accountCfg = accountId ? typexCfg?.accounts?.[accountId] : undefined;
20
+ const defaults = cfg.channels?.defaults;
21
+ return {
22
+ enabled: firstDefined(accountCfg?.enabled, typexCfg?.enabled, true) ?? true,
23
+ dmPolicy: firstDefined(accountCfg?.dmPolicy, typexCfg?.dmPolicy) ?? "pairing",
24
+ groupPolicy: firstDefined(accountCfg?.groupPolicy, typexCfg?.groupPolicy, defaults?.groupPolicy) ?? "open",
25
+ allowFrom: (accountCfg?.allowFrom ?? typexCfg?.allowFrom ?? []).map(String),
26
+ groupAllowFrom: (accountCfg?.groupAllowFrom ?? typexCfg?.groupAllowFrom ?? []).map(String),
27
+ historyLimit: firstDefined(accountCfg?.historyLimit, typexCfg?.historyLimit) ?? 10,
28
+ dmHistoryLimit: firstDefined(accountCfg?.dmHistoryLimit, typexCfg?.dmHistoryLimit) ?? 20,
29
+ textChunkLimit: firstDefined(accountCfg?.textChunkLimit, typexCfg?.textChunkLimit) ?? 2000,
30
+ chunkMode: firstDefined(accountCfg?.chunkMode, typexCfg?.chunkMode) ?? "length",
31
+ blockStreaming: firstDefined(accountCfg?.blockStreaming, typexCfg?.blockStreaming) ?? true,
32
+ streaming: firstDefined(accountCfg?.streaming, typexCfg?.streaming) ?? true,
33
+ mediaMaxMb: firstDefined(accountCfg?.mediaMaxMb, typexCfg?.mediaMaxMb) ?? 30,
34
+ groups: { ...typexCfg?.groups, ...accountCfg?.groups },
35
+ };
36
+ }
@@ -0,0 +1,15 @@
1
+ import type { TypeXClient } from "./client.js";
2
+ import type { TypeXMessageEntry } from "./types.js";
3
+ import { OpenClawConfig } from "openclaw/plugin-sdk";
4
+ export type ProcessTypeXMessageOptions = {
5
+ cfg?: OpenClawConfig;
6
+ accountId?: string;
7
+ botName?: string;
8
+ typexCfg?: Record<string, any>;
9
+ logger?: {
10
+ warn: (msg: string) => void;
11
+ info: (msg: string) => void;
12
+ error: (msg: string) => void;
13
+ } | undefined;
14
+ };
15
+ export declare function processTypeXMessage(client: TypeXClient, payload: TypeXMessageEntry, appId: string, options?: ProcessTypeXMessageOptions): Promise<void>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processTypeXMessage = processTypeXMessage;
4
+ const send_js_1 = require("./send.js");
5
+ const runtime_js_1 = require("./runtime.js");
6
+ async function processTypeXMessage(client, payload, appId, options = {}) {
7
+ const cfg = options.typexCfg;
8
+ const accountId = options.accountId ?? appId;
9
+ const logger = options.logger;
10
+ const runtime = (0, runtime_js_1.getTypeXRuntime)();
11
+ const channel = runtime.channel;
12
+ if (!channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
13
+ logger?.error(`[typex:${accountId}] dispatchReplyWithBufferedBlockDispatcher not available`);
14
+ return;
15
+ }
16
+ if (!payload || !payload.chat_id) {
17
+ logger?.warn("Received invalid event payload");
18
+ return;
19
+ }
20
+ const chatId = payload.chat_id;
21
+ const senderId = payload.sender_id;
22
+ // Use content as text for now. If content is JSON string, parse it.
23
+ let text = payload.content.text;
24
+ // Attempt simple parsing if it looks like JSON? For now assume plain text or handle in future.
25
+ // Basic logging
26
+ logger?.info(`Processing TypeX message from ${senderId} in ${chatId}`);
27
+ // Build Context for Agent
28
+ const ctx = {
29
+ Body: text,
30
+ RawBody: text,
31
+ From: senderId,
32
+ To: chatId,
33
+ SenderId: senderId,
34
+ SenderName: payload.sender_name || "User",
35
+ ChatType: "dm", // Simplified, TypeX mostly DM for now?
36
+ Provider: "typex",
37
+ Surface: "typex",
38
+ Timestamp: payload.create_time || Date.now(),
39
+ MessageSid: payload.message_id,
40
+ AccountId: accountId,
41
+ OriginatingChannel: "typex",
42
+ OriginatingTo: chatId,
43
+ };
44
+ // Dispatch to Agent
45
+ await channel.reply.dispatchReplyWithBufferedBlockDispatcher({
46
+ ctx,
47
+ cfg,
48
+ dispatcherOptions: {
49
+ channel: "typex",
50
+ accountId,
51
+ deliver: async (payload) => {
52
+ const responsePayload = payload;
53
+ // Handle text response
54
+ if (responsePayload.text) {
55
+ await (0, send_js_1.sendMessageTypeX)(client, responsePayload.text);
56
+ }
57
+ // Handle media if present in response
58
+ const mediaUrls = responsePayload.mediaUrls?.length
59
+ ? responsePayload.mediaUrls
60
+ : responsePayload.mediaUrl
61
+ ? [responsePayload.mediaUrl]
62
+ : [];
63
+ for (const mediaUrl of mediaUrls) {
64
+ await (0, send_js_1.sendMessageTypeX)(client, {}, { mediaUrl });
65
+ }
66
+ },
67
+ onError: (err) => {
68
+ logger?.error(`Reply dispatch error: ${err.message}`);
69
+ },
70
+ },
71
+ replyOptions: {
72
+ disableBlockStreaming: true,
73
+ },
74
+ });
75
+ }
@@ -0,0 +1,9 @@
1
+ import type { RuntimeEnv } from "openclaw/plugin-sdk";
2
+ export type MonitorTypeXOpts = {
3
+ account: unknown;
4
+ runtime: RuntimeEnv;
5
+ abortSignal: AbortSignal;
6
+ log?: unknown;
7
+ typexCfg: Record<string, any>;
8
+ };
9
+ export declare function monitorTypeXProvider(opts: MonitorTypeXOpts): Promise<void>;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.monitorTypeXProvider = monitorTypeXProvider;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const client_js_1 = require("./client.js");
40
+ const message_js_1 = require("./message.js");
41
+ async function monitorTypeXProvider(opts) {
42
+ try {
43
+ const { account, runtime, abortSignal, log, typexCfg } = opts;
44
+ const accountObj = account;
45
+ const { email, token, appId } = accountObj.config;
46
+ // log is unknown, cast for usage
47
+ const logger = log;
48
+ if (!token) {
49
+ logger?.warn(`[${accountObj.accountId}] No token found. Stopping monitor.`);
50
+ return;
51
+ }
52
+ // Initialize Client
53
+ const client = (0, client_js_1.getTypeXClient)(undefined, { token, skipConfigCheck: true });
54
+ logger?.info(`[${accountObj.accountId}] Starting TypeX monitor for ${email || accountObj.accountId}...`);
55
+ const dataDir = runtime.dirs?.data || "./";
56
+ const safeId = (email || accountObj.accountId || "default").replace(/[^a-z0-9]/gi, "_");
57
+ const stateFile = path.join(dataDir, `.typex_pos_${safeId}.json`);
58
+ let currentPos = 0;
59
+ try {
60
+ const data = await fs.readFile(stateFile, "utf-8");
61
+ const json = JSON.parse(data);
62
+ if (typeof json.pos === "number") {
63
+ currentPos = json.pos;
64
+ }
65
+ }
66
+ catch {
67
+ /* Ignore */
68
+ }
69
+ // --- Polling Loop ---
70
+ while (!abortSignal.aborted) {
71
+ try {
72
+ const messages = await client.fetchMessages(currentPos);
73
+ if (messages && messages.length > 0) {
74
+ for (const msg of messages) {
75
+ // Dispatch to OpenClaw via processTypeXMessage
76
+ await (0, message_js_1.processTypeXMessage)(client, msg, appId || accountObj.accountId, {
77
+ accountId: accountObj.accountId,
78
+ typexCfg,
79
+ botName: accountObj.name,
80
+ logger
81
+ });
82
+ if (typeof msg.position === "number") {
83
+ currentPos = msg.position;
84
+ }
85
+ }
86
+ // Save message position
87
+ await fs.writeFile(stateFile, JSON.stringify({ pos: currentPos }));
88
+ }
89
+ }
90
+ catch (err) {
91
+ logger?.error(`Error in TypeX polling loop: ${err instanceof Error ? err.stack : String(err)}`);
92
+ }
93
+ if (abortSignal.aborted) {
94
+ break;
95
+ }
96
+ await new Promise((resolve) => setTimeout(resolve, 3000));
97
+ logger?.info(`Stopping TypeX monitor...`);
98
+ }
99
+ }
100
+ catch (e) {
101
+ throw e;
102
+ }
103
+ }
@@ -0,0 +1,2 @@
1
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
2
+ export declare const typexOutbound: ChannelOutboundAdapter;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.typexOutbound = void 0;
4
+ const client_js_1 = require("./client.js");
5
+ const send_js_1 = require("./send.js");
6
+ exports.typexOutbound = {
7
+ deliveryMode: "direct",
8
+ // chunker: ... (optional, default might be used or I can skip for MVP)
9
+ chunkerMode: "markdown",
10
+ textChunkLimit: 2000,
11
+ sendText: async ({ to, text, accountId }) => {
12
+ const client = (0, client_js_1.getTypeXClient)(accountId ?? undefined);
13
+ const result = await (0, send_js_1.sendMessageTypeX)(client, { text });
14
+ return {
15
+ channel: "typex",
16
+ messageId: result?.message_id || "unknown",
17
+ chatId: to,
18
+ };
19
+ },
20
+ sendMedia: async ({ to, text, mediaUrl, accountId }) => {
21
+ const client = (0, client_js_1.getTypeXClient)(accountId ?? undefined);
22
+ const result = await (0, send_js_1.sendMessageTypeX)(client, { text: text || "" }, { mediaUrl });
23
+ return {
24
+ channel: "typex",
25
+ messageId: result?.message_id || "unknown",
26
+ chatId: to,
27
+ };
28
+ },
29
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Global runtime reference for the TypeX plugin.
3
+ */
4
+ import type { PluginRuntime } from "openclaw/plugin-sdk";
5
+ export declare function setTypeXRuntime(next: PluginRuntime): void;
6
+ export declare function getTypeXRuntime(): PluginRuntime;