@2en/clawly-plugins 1.16.0 → 1.16.1
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/index.ts +2 -3
- package/model-gateway-setup.ts +101 -0
- package/openclaw.plugin.json +3 -1
- package/package.json +3 -2
- package/auto-pair.ts +0 -72
package/index.ts
CHANGED
|
@@ -24,10 +24,8 @@
|
|
|
24
24
|
* Hooks:
|
|
25
25
|
* - tool_result_persist — copies TTS audio to persistent outbound directory
|
|
26
26
|
* - before_tool_call — enforces delivery fields on cron.create
|
|
27
|
-
* - gateway_start — auto-approves device pairing for Clawly mobile clients (clientId: openclaw-ios)
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
|
-
import {registerAutoPair} from './auto-pair'
|
|
31
29
|
import {registerCalendar} from './calendar'
|
|
32
30
|
import {registerClawlyCronChannel} from './channel'
|
|
33
31
|
import {registerCommands} from './command'
|
|
@@ -35,6 +33,7 @@ import {registerCronHook} from './cron-hook'
|
|
|
35
33
|
import {registerEmail} from './email'
|
|
36
34
|
import {registerGateway} from './gateway'
|
|
37
35
|
import {getGatewayConfig} from './gateway-fetch'
|
|
36
|
+
import {setupModelGateway} from './model-gateway-setup'
|
|
38
37
|
import {registerOutboundHook, registerOutboundHttpRoute, registerOutboundMethods} from './outbound'
|
|
39
38
|
import {registerTools} from './tools'
|
|
40
39
|
|
|
@@ -116,7 +115,7 @@ export default {
|
|
|
116
115
|
registerClawlyCronChannel(api)
|
|
117
116
|
registerCronHook(api)
|
|
118
117
|
registerGateway(api)
|
|
119
|
-
|
|
118
|
+
setupModelGateway(api)
|
|
120
119
|
|
|
121
120
|
// Email & calendar (optional — requires skillGatewayBaseUrl + skillGatewayToken in config)
|
|
122
121
|
const gw = getGatewayConfig(api)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On plugin init, patches openclaw.json to add the `clawly-model-gateway`
|
|
3
|
+
* model provider entry. Credentials come from pluginConfig; the model list
|
|
4
|
+
* is derived from `agents.defaults.model` / `agents.defaults.imageModel`
|
|
5
|
+
* already present in the config.
|
|
6
|
+
*
|
|
7
|
+
* This runs synchronously during plugin registration (before gateway_start).
|
|
8
|
+
* OpenClaw loads the config file once at startup, so writing before the
|
|
9
|
+
* gateway fully starts ensures the provider is active on first boot.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
|
|
15
|
+
import type {PluginApi} from './index'
|
|
16
|
+
|
|
17
|
+
const PROVIDER_NAME = 'clawly-model-gateway'
|
|
18
|
+
|
|
19
|
+
function resolveStateDir(api: PluginApi): string {
|
|
20
|
+
return api.runtime.state?.resolveStateDir?.(process.env) ?? process.env.OPENCLAW_STATE_DIR ?? ''
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readOpenclawConfig(configPath: string): Record<string, unknown> {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
26
|
+
} catch {
|
|
27
|
+
return {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeOpenclawConfig(configPath: string, config: Record<string, unknown>) {
|
|
32
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function setupModelGateway(api: PluginApi): void {
|
|
36
|
+
const cfg = api.pluginConfig as Record<string, unknown> | undefined
|
|
37
|
+
const baseUrl =
|
|
38
|
+
typeof cfg?.modelGatewayBaseUrl === 'string' ? cfg.modelGatewayBaseUrl.replace(/\/$/, '') : ''
|
|
39
|
+
const token = typeof cfg?.modelGatewayToken === 'string' ? cfg.modelGatewayToken : ''
|
|
40
|
+
|
|
41
|
+
if (!baseUrl || !token) {
|
|
42
|
+
api.logger.info('Model gateway not configured (missing baseUrl or token), skipping.')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const stateDir = resolveStateDir(api)
|
|
47
|
+
if (!stateDir) {
|
|
48
|
+
api.logger.warn('Cannot resolve state dir — model gateway setup skipped.')
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const configPath = path.join(stateDir, 'openclaw.json')
|
|
53
|
+
const config = readOpenclawConfig(configPath)
|
|
54
|
+
|
|
55
|
+
// Already configured — skip
|
|
56
|
+
if (config.models?.providers?.[PROVIDER_NAME]) {
|
|
57
|
+
api.logger.info('Model gateway provider already configured.')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Derive model IDs from agents.defaults
|
|
62
|
+
const defaultModelFull: string = (config.agents as any)?.defaults?.model?.primary ?? ''
|
|
63
|
+
const imageModelFull: string = (config.agents as any)?.defaults?.imageModel?.primary ?? ''
|
|
64
|
+
|
|
65
|
+
const prefix = `${PROVIDER_NAME}/`
|
|
66
|
+
const defaultModel = defaultModelFull.startsWith(prefix)
|
|
67
|
+
? defaultModelFull.slice(prefix.length)
|
|
68
|
+
: defaultModelFull
|
|
69
|
+
const imageModel = imageModelFull.startsWith(prefix)
|
|
70
|
+
? imageModelFull.slice(prefix.length)
|
|
71
|
+
: imageModelFull
|
|
72
|
+
|
|
73
|
+
if (!defaultModel) {
|
|
74
|
+
api.logger.warn('No default model found in agents.defaults — model gateway setup skipped.')
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const models =
|
|
79
|
+
defaultModel === imageModel || !imageModel
|
|
80
|
+
? [{id: defaultModel, name: defaultModel, input: ['text', 'image']}]
|
|
81
|
+
: [
|
|
82
|
+
{id: defaultModel, name: defaultModel, input: ['text']},
|
|
83
|
+
{id: imageModel, name: imageModel, input: ['text', 'image']},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
if (!config.models) config.models = {}
|
|
87
|
+
if (!(config.models as any).providers) (config.models as any).providers = {}
|
|
88
|
+
;(config.models as any).providers[PROVIDER_NAME] = {
|
|
89
|
+
baseUrl,
|
|
90
|
+
apiKey: token,
|
|
91
|
+
api: 'openai-completions',
|
|
92
|
+
models,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
writeOpenclawConfig(configPath, config)
|
|
97
|
+
api.logger.info(`Model gateway provider configured: ${baseUrl} with ${models.length} model(s).`)
|
|
98
|
+
} catch (err) {
|
|
99
|
+
api.logger.error(`Failed to setup model gateway: ${(err as Error).message}`)
|
|
100
|
+
}
|
|
101
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -47,7 +47,9 @@
|
|
|
47
47
|
"defaultTimeoutMs": { "type": "number", "minimum": 1000 },
|
|
48
48
|
"configPath": { "type": "string" },
|
|
49
49
|
"skillGatewayBaseUrl": { "type": "string" },
|
|
50
|
-
"skillGatewayToken": { "type": "string" }
|
|
50
|
+
"skillGatewayToken": { "type": "string" },
|
|
51
|
+
"modelGatewayBaseUrl": { "type": "string" },
|
|
52
|
+
"modelGatewayToken": { "type": "string" }
|
|
51
53
|
},
|
|
52
54
|
"required": []
|
|
53
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2en/clawly-plugins",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
"lib",
|
|
18
18
|
"tools",
|
|
19
19
|
"index.ts",
|
|
20
|
-
"auto-pair.ts",
|
|
21
20
|
"calendar.ts",
|
|
22
21
|
"channel.ts",
|
|
23
22
|
"cron-hook.ts",
|
|
24
23
|
"email.ts",
|
|
25
24
|
"gateway-fetch.ts",
|
|
26
25
|
"outbound.ts",
|
|
26
|
+
"auto-pair.ts",
|
|
27
|
+
"model-gateway-setup.ts",
|
|
27
28
|
"openclaw.plugin.json"
|
|
28
29
|
],
|
|
29
30
|
"publishConfig": {
|
package/auto-pair.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type {PluginApi} from './index'
|
|
2
|
-
|
|
3
|
-
const AUTO_APPROVE_CLIENT_IDS = new Set(['openclaw-ios'])
|
|
4
|
-
const POLL_INTERVAL_MS = 3_000
|
|
5
|
-
|
|
6
|
-
type PendingRequest = {
|
|
7
|
-
requestId: string
|
|
8
|
-
deviceId: string
|
|
9
|
-
clientId?: string
|
|
10
|
-
displayName?: string
|
|
11
|
-
platform?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type PairedDevice = {
|
|
15
|
-
deviceId: string
|
|
16
|
-
displayName?: string
|
|
17
|
-
platform?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type DevicePairingList = {
|
|
21
|
-
pending: PendingRequest[]
|
|
22
|
-
paired: PairedDevice[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function registerAutoPair(api: PluginApi) {
|
|
26
|
-
let timer: ReturnType<typeof setInterval> | null = null
|
|
27
|
-
let sdk: {
|
|
28
|
-
listDevicePairing: () => Promise<DevicePairingList>
|
|
29
|
-
approveDevicePairing: (
|
|
30
|
-
requestId: string,
|
|
31
|
-
) => Promise<{requestId: string; device: PairedDevice} | null>
|
|
32
|
-
} | null = null
|
|
33
|
-
|
|
34
|
-
api.on('gateway_start', async () => {
|
|
35
|
-
try {
|
|
36
|
-
sdk = await import('openclaw/plugin-sdk')
|
|
37
|
-
} catch {
|
|
38
|
-
api.logger.warn('auto-pair: openclaw/plugin-sdk not available, skipping')
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
api.logger.info('auto-pair: started polling for pending pairing requests')
|
|
43
|
-
|
|
44
|
-
timer = setInterval(async () => {
|
|
45
|
-
if (!sdk) return
|
|
46
|
-
try {
|
|
47
|
-
const {pending} = await sdk.listDevicePairing()
|
|
48
|
-
for (const req of pending) {
|
|
49
|
-
if (req.clientId && AUTO_APPROVE_CLIENT_IDS.has(req.clientId)) {
|
|
50
|
-
const result = await sdk.approveDevicePairing(req.requestId)
|
|
51
|
-
if (result) {
|
|
52
|
-
api.logger.info(
|
|
53
|
-
`auto-pair: approved device=${result.device.deviceId} ` +
|
|
54
|
-
`name=${result.device.displayName ?? 'unknown'} ` +
|
|
55
|
-
`platform=${result.device.platform ?? 'unknown'}`,
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
} catch (err) {
|
|
61
|
-
api.logger.warn(`auto-pair: poll error: ${String(err)}`)
|
|
62
|
-
}
|
|
63
|
-
}, POLL_INTERVAL_MS)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
api.on('gateway_stop', () => {
|
|
67
|
-
if (timer) {
|
|
68
|
-
clearInterval(timer)
|
|
69
|
-
timer = null
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
}
|