@workclaw/openclaw-workchat 1.0.0 → 1.0.3
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 +210 -210
- package/index.js +49600 -0
- package/index.ts +19 -19
- package/openclaw.plugin.json +25 -25
- package/package.json +64 -63
- package/src/api/api.ts +260 -260
- package/src/api/types.ts +56 -56
- package/src/auth/accounts.ts +290 -290
- package/src/auth/bind-agent.ts +70 -70
- package/src/auth/login-qr-terminal.ts +16 -16
- package/src/auth/login-qr.ts +475 -475
- package/src/auth/pairing.ts +133 -133
- package/src/channel.ts +282 -282
- package/src/compat.ts +75 -75
- package/src/config/config-schema.ts +29 -29
- package/src/messaging/debug-mode.ts +57 -57
- package/src/messaging/error-notice.ts +30 -30
- package/src/messaging/inbound.ts +174 -174
- package/src/messaging/markdown-filter.ts +361 -361
- package/src/messaging/outbound-hooks.ts +84 -84
- package/src/messaging/process-message.ts +530 -530
- package/src/messaging/send.ts +119 -119
- package/src/messaging/slash-commands.ts +105 -105
- package/src/monitor/monitor.ts +261 -261
- package/src/storage/state-dir.ts +11 -11
- package/src/util/logger.ts +147 -147
- package/src/util/random.ts +9 -9
- package/src/util/redact.ts +39 -39
- package/src/vendor.d.ts +10 -10
package/src/auth/pairing.ts
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { withFileLock } from "openclaw/plugin-sdk/infra-runtime";
|
|
5
|
-
|
|
6
|
-
import { resolveStateDir } from "../storage/state-dir.js";
|
|
7
|
-
import { logger } from "../util/logger.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Resolve the framework credentials directory (mirrors core resolveOAuthDir).
|
|
11
|
-
* Path: $OPENCLAW_OAUTH_DIR || $OPENCLAW_STATE_DIR/credentials || ~/.openclaw/credentials
|
|
12
|
-
*/
|
|
13
|
-
function resolveCredentialsDir(): string {
|
|
14
|
-
const override = process.env.OPENCLAW_OAUTH_DIR?.trim();
|
|
15
|
-
if (override) return override;
|
|
16
|
-
return path.join(resolveStateDir(), "credentials");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Sanitize a channel/account key for safe use in filenames (mirrors core safeChannelKey).
|
|
21
|
-
*/
|
|
22
|
-
function safeKey(raw: string): string {
|
|
23
|
-
const trimmed = raw.trim().toLowerCase();
|
|
24
|
-
if (!trimmed) throw new Error("invalid key for allowFrom path");
|
|
25
|
-
const safe = trimmed.replace(/[\\/:*?"<>|]/g, "_").replace(/\.\./g, "_");
|
|
26
|
-
if (!safe || safe === "_") throw new Error("invalid key for allowFrom path");
|
|
27
|
-
return safe;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Resolve the framework allowFrom file path for a given account.
|
|
32
|
-
* Mirrors: `resolveAllowFromPath(channel, env, accountId)` from core.
|
|
33
|
-
* Path: `<credDir>/openclaw-workchat-<accountId>-allowFrom.json`
|
|
34
|
-
*/
|
|
35
|
-
export function resolveFrameworkAllowFromPath(accountId: string): string {
|
|
36
|
-
const base = safeKey("openclaw-workchat");
|
|
37
|
-
const safeAccount = safeKey(accountId);
|
|
38
|
-
return path.join(resolveCredentialsDir(), `${base}-${safeAccount}-allowFrom.json`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type AllowFromFileContent = {
|
|
42
|
-
version: number;
|
|
43
|
-
allowFrom: string[];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Read the framework allowFrom list for an account (user IDs authorized via pairing).
|
|
48
|
-
* Returns an empty array when the file is missing or unreadable.
|
|
49
|
-
*/
|
|
50
|
-
export function readFrameworkAllowFromList(accountId: string): string[] {
|
|
51
|
-
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
52
|
-
try {
|
|
53
|
-
if (!fs.existsSync(filePath)) return [];
|
|
54
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
55
|
-
const parsed = JSON.parse(raw) as AllowFromFileContent;
|
|
56
|
-
if (Array.isArray(parsed.allowFrom)) {
|
|
57
|
-
return parsed.allowFrom.filter((id): id is string => typeof id === "string" && id.trim() !== "");
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// best-effort
|
|
61
|
-
}
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Remove the framework allowFrom store for an account (binding teardown). */
|
|
66
|
-
export function clearFrameworkAllowFromList(accountId: string): void {
|
|
67
|
-
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
68
|
-
try {
|
|
69
|
-
if (fs.existsSync(filePath)) {
|
|
70
|
-
fs.unlinkSync(filePath);
|
|
71
|
-
logger.info(`clearFrameworkAllowFromList: removed ${filePath}`);
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
logger.warn(`clearFrameworkAllowFromList: failed to remove ${filePath}: ${String(err)}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** File lock options matching the framework's pairing store lock settings. */
|
|
79
|
-
const LOCK_OPTIONS = {
|
|
80
|
-
retries: { retries: 3, factor: 2, minTimeout: 100, maxTimeout: 2000 },
|
|
81
|
-
stale: 10_000,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Register a user ID in the framework's channel allowFrom store.
|
|
86
|
-
* This writes directly to the same JSON file that `readChannelAllowFromStore` reads,
|
|
87
|
-
* making the user visible to the framework authorization pipeline.
|
|
88
|
-
*
|
|
89
|
-
* Uses file locking to avoid races with concurrent readers/writers.
|
|
90
|
-
*/
|
|
91
|
-
export async function registerUserInFrameworkStore(params: {
|
|
92
|
-
accountId: string;
|
|
93
|
-
userId: string;
|
|
94
|
-
}): Promise<{ changed: boolean }> {
|
|
95
|
-
const { accountId, userId } = params;
|
|
96
|
-
const trimmedUserId = userId.trim();
|
|
97
|
-
if (!trimmedUserId) return { changed: false };
|
|
98
|
-
|
|
99
|
-
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
100
|
-
|
|
101
|
-
const dir = path.dirname(filePath);
|
|
102
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
103
|
-
|
|
104
|
-
// Ensure the file exists before locking
|
|
105
|
-
if (!fs.existsSync(filePath)) {
|
|
106
|
-
const initial: AllowFromFileContent = { version: 1, allowFrom: [] };
|
|
107
|
-
fs.writeFileSync(filePath, JSON.stringify(initial, null, 2), "utf-8");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return await withFileLock(filePath, LOCK_OPTIONS, async () => {
|
|
111
|
-
let content: AllowFromFileContent = { version: 1, allowFrom: [] };
|
|
112
|
-
try {
|
|
113
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
114
|
-
const parsed = JSON.parse(raw) as AllowFromFileContent;
|
|
115
|
-
if (Array.isArray(parsed.allowFrom)) {
|
|
116
|
-
content = parsed;
|
|
117
|
-
}
|
|
118
|
-
} catch {
|
|
119
|
-
// If read/parse fails, start fresh
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (content.allowFrom.includes(trimmedUserId)) {
|
|
123
|
-
return { changed: false };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
content.allowFrom.push(trimmedUserId);
|
|
127
|
-
fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
|
|
128
|
-
logger.info(
|
|
129
|
-
`registerUserInFrameworkStore: added userId=${trimmedUserId} accountId=${accountId} path=${filePath}`,
|
|
130
|
-
);
|
|
131
|
-
return { changed: true };
|
|
132
|
-
});
|
|
133
|
-
}
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { withFileLock } from "openclaw/plugin-sdk/infra-runtime";
|
|
5
|
+
|
|
6
|
+
import { resolveStateDir } from "../storage/state-dir.js";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the framework credentials directory (mirrors core resolveOAuthDir).
|
|
11
|
+
* Path: $OPENCLAW_OAUTH_DIR || $OPENCLAW_STATE_DIR/credentials || ~/.openclaw/credentials
|
|
12
|
+
*/
|
|
13
|
+
function resolveCredentialsDir(): string {
|
|
14
|
+
const override = process.env.OPENCLAW_OAUTH_DIR?.trim();
|
|
15
|
+
if (override) return override;
|
|
16
|
+
return path.join(resolveStateDir(), "credentials");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sanitize a channel/account key for safe use in filenames (mirrors core safeChannelKey).
|
|
21
|
+
*/
|
|
22
|
+
function safeKey(raw: string): string {
|
|
23
|
+
const trimmed = raw.trim().toLowerCase();
|
|
24
|
+
if (!trimmed) throw new Error("invalid key for allowFrom path");
|
|
25
|
+
const safe = trimmed.replace(/[\\/:*?"<>|]/g, "_").replace(/\.\./g, "_");
|
|
26
|
+
if (!safe || safe === "_") throw new Error("invalid key for allowFrom path");
|
|
27
|
+
return safe;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the framework allowFrom file path for a given account.
|
|
32
|
+
* Mirrors: `resolveAllowFromPath(channel, env, accountId)` from core.
|
|
33
|
+
* Path: `<credDir>/openclaw-workchat-<accountId>-allowFrom.json`
|
|
34
|
+
*/
|
|
35
|
+
export function resolveFrameworkAllowFromPath(accountId: string): string {
|
|
36
|
+
const base = safeKey("openclaw-workchat");
|
|
37
|
+
const safeAccount = safeKey(accountId);
|
|
38
|
+
return path.join(resolveCredentialsDir(), `${base}-${safeAccount}-allowFrom.json`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type AllowFromFileContent = {
|
|
42
|
+
version: number;
|
|
43
|
+
allowFrom: string[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read the framework allowFrom list for an account (user IDs authorized via pairing).
|
|
48
|
+
* Returns an empty array when the file is missing or unreadable.
|
|
49
|
+
*/
|
|
50
|
+
export function readFrameworkAllowFromList(accountId: string): string[] {
|
|
51
|
+
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
52
|
+
try {
|
|
53
|
+
if (!fs.existsSync(filePath)) return [];
|
|
54
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
55
|
+
const parsed = JSON.parse(raw) as AllowFromFileContent;
|
|
56
|
+
if (Array.isArray(parsed.allowFrom)) {
|
|
57
|
+
return parsed.allowFrom.filter((id): id is string => typeof id === "string" && id.trim() !== "");
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// best-effort
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Remove the framework allowFrom store for an account (binding teardown). */
|
|
66
|
+
export function clearFrameworkAllowFromList(accountId: string): void {
|
|
67
|
+
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(filePath)) {
|
|
70
|
+
fs.unlinkSync(filePath);
|
|
71
|
+
logger.info(`clearFrameworkAllowFromList: removed ${filePath}`);
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.warn(`clearFrameworkAllowFromList: failed to remove ${filePath}: ${String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** File lock options matching the framework's pairing store lock settings. */
|
|
79
|
+
const LOCK_OPTIONS = {
|
|
80
|
+
retries: { retries: 3, factor: 2, minTimeout: 100, maxTimeout: 2000 },
|
|
81
|
+
stale: 10_000,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Register a user ID in the framework's channel allowFrom store.
|
|
86
|
+
* This writes directly to the same JSON file that `readChannelAllowFromStore` reads,
|
|
87
|
+
* making the user visible to the framework authorization pipeline.
|
|
88
|
+
*
|
|
89
|
+
* Uses file locking to avoid races with concurrent readers/writers.
|
|
90
|
+
*/
|
|
91
|
+
export async function registerUserInFrameworkStore(params: {
|
|
92
|
+
accountId: string;
|
|
93
|
+
userId: string;
|
|
94
|
+
}): Promise<{ changed: boolean }> {
|
|
95
|
+
const { accountId, userId } = params;
|
|
96
|
+
const trimmedUserId = userId.trim();
|
|
97
|
+
if (!trimmedUserId) return { changed: false };
|
|
98
|
+
|
|
99
|
+
const filePath = resolveFrameworkAllowFromPath(accountId);
|
|
100
|
+
|
|
101
|
+
const dir = path.dirname(filePath);
|
|
102
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
|
|
104
|
+
// Ensure the file exists before locking
|
|
105
|
+
if (!fs.existsSync(filePath)) {
|
|
106
|
+
const initial: AllowFromFileContent = { version: 1, allowFrom: [] };
|
|
107
|
+
fs.writeFileSync(filePath, JSON.stringify(initial, null, 2), "utf-8");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return await withFileLock(filePath, LOCK_OPTIONS, async () => {
|
|
111
|
+
let content: AllowFromFileContent = { version: 1, allowFrom: [] };
|
|
112
|
+
try {
|
|
113
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
114
|
+
const parsed = JSON.parse(raw) as AllowFromFileContent;
|
|
115
|
+
if (Array.isArray(parsed.allowFrom)) {
|
|
116
|
+
content = parsed;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// If read/parse fails, start fresh
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (content.allowFrom.includes(trimmedUserId)) {
|
|
123
|
+
return { changed: false };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
content.allowFrom.push(trimmedUserId);
|
|
127
|
+
fs.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
|
|
128
|
+
logger.info(
|
|
129
|
+
`registerUserInFrameworkStore: added userId=${trimmedUserId} accountId=${accountId} path=${filePath}`,
|
|
130
|
+
);
|
|
131
|
+
return { changed: true };
|
|
132
|
+
});
|
|
133
|
+
}
|