@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/compat.ts
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime host-version compatibility check for openclaw-workchat.
|
|
3
|
-
*
|
|
4
|
-
* OpenClaw uses a date-based version format: YYYY.M.DD (e.g. 2026.3.22).
|
|
5
|
-
* This module parses that format and validates the running host is within
|
|
6
|
-
* the supported range for this plugin version.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { logger } from "./util/logger.js";
|
|
10
|
-
|
|
11
|
-
export const SUPPORTED_HOST_MIN = "2026.3.22";
|
|
12
|
-
|
|
13
|
-
export interface OpenClawVersion {
|
|
14
|
-
year: number;
|
|
15
|
-
month: number;
|
|
16
|
-
day: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Parse an OpenClaw date version string (e.g. "2026.3.22") into components.
|
|
21
|
-
* Returns null for unparseable strings.
|
|
22
|
-
*/
|
|
23
|
-
export function parseOpenClawVersion(version: string): OpenClawVersion | null {
|
|
24
|
-
// Strip any pre-release suffix (e.g. "2026.3.22-beta.1" -> "2026.3.22")
|
|
25
|
-
const base = version.trim().split("-")[0];
|
|
26
|
-
const parts = base.split(".");
|
|
27
|
-
if (parts.length !== 3) return null;
|
|
28
|
-
const [year, month, day] = parts.map(Number);
|
|
29
|
-
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) return null;
|
|
30
|
-
return { year, month, day };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Compare two parsed versions. Returns -1 | 0 | 1.
|
|
35
|
-
*/
|
|
36
|
-
export function compareVersions(a: OpenClawVersion, b: OpenClawVersion): -1 | 0 | 1 {
|
|
37
|
-
for (const key of ["year", "month", "day"] as const) {
|
|
38
|
-
if (a[key] < b[key]) return -1;
|
|
39
|
-
if (a[key] > b[key]) return 1;
|
|
40
|
-
}
|
|
41
|
-
return 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Check whether a host version string is >= SUPPORTED_HOST_MIN.
|
|
46
|
-
*/
|
|
47
|
-
export function isHostVersionSupported(hostVersion: string): boolean {
|
|
48
|
-
const host = parseOpenClawVersion(hostVersion);
|
|
49
|
-
if (!host) return false;
|
|
50
|
-
const min = parseOpenClawVersion(SUPPORTED_HOST_MIN)!;
|
|
51
|
-
return compareVersions(host, min) >= 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fail-fast guard. Call at the very start of `register()` to prevent the
|
|
56
|
-
* plugin from loading on an incompatible host.
|
|
57
|
-
*
|
|
58
|
-
* @throws {Error} with a human-readable message when the host is out of range.
|
|
59
|
-
*/
|
|
60
|
-
export function assertHostCompatibility(hostVersion: string | undefined): void {
|
|
61
|
-
if (!hostVersion || hostVersion === "unknown") {
|
|
62
|
-
logger.warn(
|
|
63
|
-
`[compat] Could not determine host OpenClaw version; skipping compatibility check.`,
|
|
64
|
-
);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (isHostVersionSupported(hostVersion)) {
|
|
68
|
-
logger.info(`[compat] Host OpenClaw ${hostVersion} >= ${SUPPORTED_HOST_MIN}, OK.`);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
throw new Error(
|
|
72
|
-
`This version of openclaw-workchat requires OpenClaw >=${SUPPORTED_HOST_MIN}, ` +
|
|
73
|
-
`but found ${hostVersion}. Please upgrade OpenClaw.`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Runtime host-version compatibility check for openclaw-workchat.
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw uses a date-based version format: YYYY.M.DD (e.g. 2026.3.22).
|
|
5
|
+
* This module parses that format and validates the running host is within
|
|
6
|
+
* the supported range for this plugin version.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { logger } from "./util/logger.js";
|
|
10
|
+
|
|
11
|
+
export const SUPPORTED_HOST_MIN = "2026.3.22";
|
|
12
|
+
|
|
13
|
+
export interface OpenClawVersion {
|
|
14
|
+
year: number;
|
|
15
|
+
month: number;
|
|
16
|
+
day: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse an OpenClaw date version string (e.g. "2026.3.22") into components.
|
|
21
|
+
* Returns null for unparseable strings.
|
|
22
|
+
*/
|
|
23
|
+
export function parseOpenClawVersion(version: string): OpenClawVersion | null {
|
|
24
|
+
// Strip any pre-release suffix (e.g. "2026.3.22-beta.1" -> "2026.3.22")
|
|
25
|
+
const base = version.trim().split("-")[0];
|
|
26
|
+
const parts = base.split(".");
|
|
27
|
+
if (parts.length !== 3) return null;
|
|
28
|
+
const [year, month, day] = parts.map(Number);
|
|
29
|
+
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) return null;
|
|
30
|
+
return { year, month, day };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compare two parsed versions. Returns -1 | 0 | 1.
|
|
35
|
+
*/
|
|
36
|
+
export function compareVersions(a: OpenClawVersion, b: OpenClawVersion): -1 | 0 | 1 {
|
|
37
|
+
for (const key of ["year", "month", "day"] as const) {
|
|
38
|
+
if (a[key] < b[key]) return -1;
|
|
39
|
+
if (a[key] > b[key]) return 1;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check whether a host version string is >= SUPPORTED_HOST_MIN.
|
|
46
|
+
*/
|
|
47
|
+
export function isHostVersionSupported(hostVersion: string): boolean {
|
|
48
|
+
const host = parseOpenClawVersion(hostVersion);
|
|
49
|
+
if (!host) return false;
|
|
50
|
+
const min = parseOpenClawVersion(SUPPORTED_HOST_MIN)!;
|
|
51
|
+
return compareVersions(host, min) >= 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fail-fast guard. Call at the very start of `register()` to prevent the
|
|
56
|
+
* plugin from loading on an incompatible host.
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} with a human-readable message when the host is out of range.
|
|
59
|
+
*/
|
|
60
|
+
export function assertHostCompatibility(hostVersion: string | undefined): void {
|
|
61
|
+
if (!hostVersion || hostVersion === "unknown") {
|
|
62
|
+
logger.warn(
|
|
63
|
+
`[compat] Could not determine host OpenClaw version; skipping compatibility check.`,
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (isHostVersionSupported(hostVersion)) {
|
|
68
|
+
logger.info(`[compat] Host OpenClaw ${hostVersion} >= ${SUPPORTED_HOST_MIN}, OK.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
throw new Error(
|
|
72
|
+
`This version of openclaw-workchat requires OpenClaw >=${SUPPORTED_HOST_MIN}, ` +
|
|
73
|
+
`but found ${hostVersion}. Please upgrade OpenClaw.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
import { DEFAULT_BASE_URL, DEFAULT_WS_URL } from "../api/types.js";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Zod config schema
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
const workchatAccountSchema = z.object({
|
|
10
|
-
name: z.string().optional(),
|
|
11
|
-
enabled: z.boolean().optional(),
|
|
12
|
-
agentId: z.union([z.string(), z.number()]).optional(),
|
|
13
|
-
userId: z.union([z.string(), z.number()]).optional(),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
/** Top-level workchat config schema (appKey/appSecret are env placeholders). */
|
|
17
|
-
export const WorkChatConfigSchema = workchatAccountSchema.extend({
|
|
18
|
-
baseUrl: z.string().default(DEFAULT_BASE_URL),
|
|
19
|
-
websocketUrl: z.string().default(DEFAULT_WS_URL),
|
|
20
|
-
/** `${WORKCHAT_API_KEY}` secret placeholder — resolved from gateway env. */
|
|
21
|
-
appKey: z.string().optional(),
|
|
22
|
-
/** `${WORKCHAT_APP_SECRET}` secret placeholder — resolved from gateway env. */
|
|
23
|
-
appSecret: z.string().optional(),
|
|
24
|
-
allowInsecureTls: z.boolean().optional(),
|
|
25
|
-
requestTimeout: z.number().int().positive().optional(),
|
|
26
|
-
accounts: z.record(z.string(), workchatAccountSchema).optional(),
|
|
27
|
-
/** ISO 8601; bumped on each successful binding to refresh gateway config from disk. */
|
|
28
|
-
channelConfigUpdatedAt: z.string().optional(),
|
|
29
|
-
});
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_BASE_URL, DEFAULT_WS_URL } from "../api/types.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Zod config schema
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const workchatAccountSchema = z.object({
|
|
10
|
+
name: z.string().optional(),
|
|
11
|
+
enabled: z.boolean().optional(),
|
|
12
|
+
agentId: z.union([z.string(), z.number()]).optional(),
|
|
13
|
+
userId: z.union([z.string(), z.number()]).optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Top-level workchat config schema (appKey/appSecret are env placeholders). */
|
|
17
|
+
export const WorkChatConfigSchema = workchatAccountSchema.extend({
|
|
18
|
+
baseUrl: z.string().default(DEFAULT_BASE_URL),
|
|
19
|
+
websocketUrl: z.string().default(DEFAULT_WS_URL),
|
|
20
|
+
/** `${WORKCHAT_API_KEY}` secret placeholder — resolved from gateway env. */
|
|
21
|
+
appKey: z.string().optional(),
|
|
22
|
+
/** `${WORKCHAT_APP_SECRET}` secret placeholder — resolved from gateway env. */
|
|
23
|
+
appSecret: z.string().optional(),
|
|
24
|
+
allowInsecureTls: z.boolean().optional(),
|
|
25
|
+
requestTimeout: z.number().int().positive().optional(),
|
|
26
|
+
accounts: z.record(z.string(), workchatAccountSchema).optional(),
|
|
27
|
+
/** ISO 8601; bumped on each successful binding to refresh gateway config from disk. */
|
|
28
|
+
channelConfigUpdatedAt: z.string().optional(),
|
|
29
|
+
});
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-bot debug mode toggle, persisted to disk so it survives gateway restarts.
|
|
3
|
-
*
|
|
4
|
-
* State file: `<stateDir>/openclaw-workchat/debug-mode.json`
|
|
5
|
-
* Format: `{ "accounts": { "<accountId>": true, ... } }`
|
|
6
|
-
*
|
|
7
|
-
* When enabled, processOneFrame appends a timing summary after each
|
|
8
|
-
* AI reply is delivered to the user.
|
|
9
|
-
*/
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
|
|
13
|
-
import { resolveStateDir } from "../storage/state-dir.js";
|
|
14
|
-
import { logger } from "../util/logger.js";
|
|
15
|
-
|
|
16
|
-
interface DebugModeState {
|
|
17
|
-
accounts: Record<string, boolean>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function resolveDebugModePath(): string {
|
|
21
|
-
return path.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function loadState(): DebugModeState {
|
|
25
|
-
try {
|
|
26
|
-
const raw = fs.readFileSync(resolveDebugModePath(), "utf-8");
|
|
27
|
-
const parsed = JSON.parse(raw) as DebugModeState;
|
|
28
|
-
if (parsed && typeof parsed.accounts === "object") return parsed;
|
|
29
|
-
} catch {
|
|
30
|
-
// missing or corrupt — start fresh
|
|
31
|
-
}
|
|
32
|
-
return { accounts: {} };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function saveState(state: DebugModeState): void {
|
|
36
|
-
const filePath = resolveDebugModePath();
|
|
37
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
-
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Toggle debug mode for a bot account. Returns the new state. */
|
|
42
|
-
export function toggleDebugMode(accountId: string): boolean {
|
|
43
|
-
const state = loadState();
|
|
44
|
-
const next = !state.accounts[accountId];
|
|
45
|
-
state.accounts[accountId] = next;
|
|
46
|
-
try {
|
|
47
|
-
saveState(state);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
logger.error(`debug-mode: failed to persist state: ${String(err)}`);
|
|
50
|
-
}
|
|
51
|
-
return next;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Check whether debug mode is active for a bot account. */
|
|
55
|
-
export function isDebugMode(accountId: string): boolean {
|
|
56
|
-
return loadState().accounts[accountId] === true;
|
|
57
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Per-bot debug mode toggle, persisted to disk so it survives gateway restarts.
|
|
3
|
+
*
|
|
4
|
+
* State file: `<stateDir>/openclaw-workchat/debug-mode.json`
|
|
5
|
+
* Format: `{ "accounts": { "<accountId>": true, ... } }`
|
|
6
|
+
*
|
|
7
|
+
* When enabled, processOneFrame appends a timing summary after each
|
|
8
|
+
* AI reply is delivered to the user.
|
|
9
|
+
*/
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import { resolveStateDir } from "../storage/state-dir.js";
|
|
14
|
+
import { logger } from "../util/logger.js";
|
|
15
|
+
|
|
16
|
+
interface DebugModeState {
|
|
17
|
+
accounts: Record<string, boolean>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveDebugModePath(): string {
|
|
21
|
+
return path.join(resolveStateDir(), "openclaw-workchat", "debug-mode.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadState(): DebugModeState {
|
|
25
|
+
try {
|
|
26
|
+
const raw = fs.readFileSync(resolveDebugModePath(), "utf-8");
|
|
27
|
+
const parsed = JSON.parse(raw) as DebugModeState;
|
|
28
|
+
if (parsed && typeof parsed.accounts === "object") return parsed;
|
|
29
|
+
} catch {
|
|
30
|
+
// missing or corrupt — start fresh
|
|
31
|
+
}
|
|
32
|
+
return { accounts: {} };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function saveState(state: DebugModeState): void {
|
|
36
|
+
const filePath = resolveDebugModePath();
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Toggle debug mode for a bot account. Returns the new state. */
|
|
42
|
+
export function toggleDebugMode(accountId: string): boolean {
|
|
43
|
+
const state = loadState();
|
|
44
|
+
const next = !state.accounts[accountId];
|
|
45
|
+
state.accounts[accountId] = next;
|
|
46
|
+
try {
|
|
47
|
+
saveState(state);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.error(`debug-mode: failed to persist state: ${String(err)}`);
|
|
50
|
+
}
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Check whether debug mode is active for a bot account. */
|
|
55
|
+
export function isDebugMode(accountId: string): boolean {
|
|
56
|
+
return loadState().accounts[accountId] === true;
|
|
57
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import type { WorkChatApiOptions } from "../api/api.js";
|
|
2
|
-
import { logger } from "../util/logger.js";
|
|
3
|
-
|
|
4
|
-
import { pushWorkChatMessage } from "./send.js";
|
|
5
|
-
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Send a plain-text error notice back to the user.
|
|
9
|
-
* Fire-and-forget: errors are logged but never thrown, so callers stay unaffected.
|
|
10
|
-
*/
|
|
11
|
-
export async function sendWorkChatErrorNotice(params: {
|
|
12
|
-
account: ResolvedWorkChatAccount;
|
|
13
|
-
to: string;
|
|
14
|
-
message: string;
|
|
15
|
-
agentId?: string;
|
|
16
|
-
apiOpts?: WorkChatApiOptions;
|
|
17
|
-
errLog: (m: string) => void;
|
|
18
|
-
}): Promise<void> {
|
|
19
|
-
try {
|
|
20
|
-
await pushWorkChatMessage({
|
|
21
|
-
account: params.account,
|
|
22
|
-
to: params.to,
|
|
23
|
-
text: params.message,
|
|
24
|
-
agentId: params.agentId,
|
|
25
|
-
});
|
|
26
|
-
logger.debug(`sendWorkChatErrorNotice: sent to=${params.to}`);
|
|
27
|
-
} catch (err) {
|
|
28
|
-
params.errLog(`[workchat] sendWorkChatErrorNotice failed to=${params.to}: ${String(err)}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
import type { WorkChatApiOptions } from "../api/api.js";
|
|
2
|
+
import { logger } from "../util/logger.js";
|
|
3
|
+
|
|
4
|
+
import { pushWorkChatMessage } from "./send.js";
|
|
5
|
+
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Send a plain-text error notice back to the user.
|
|
9
|
+
* Fire-and-forget: errors are logged but never thrown, so callers stay unaffected.
|
|
10
|
+
*/
|
|
11
|
+
export async function sendWorkChatErrorNotice(params: {
|
|
12
|
+
account: ResolvedWorkChatAccount;
|
|
13
|
+
to: string;
|
|
14
|
+
message: string;
|
|
15
|
+
agentId?: string;
|
|
16
|
+
apiOpts?: WorkChatApiOptions;
|
|
17
|
+
errLog: (m: string) => void;
|
|
18
|
+
}): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
await pushWorkChatMessage({
|
|
21
|
+
account: params.account,
|
|
22
|
+
to: params.to,
|
|
23
|
+
text: params.message,
|
|
24
|
+
agentId: params.agentId,
|
|
25
|
+
});
|
|
26
|
+
logger.debug(`sendWorkChatErrorNotice: sent to=${params.to}`);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
params.errLog(`[workchat] sendWorkChatErrorNotice failed to=${params.to}: ${String(err)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|