@2en/clawly-plugins 1.26.1-beta.0 → 1.26.1-beta.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/config-setup.ts +88 -10
- package/gateway/config-timezone.ts +56 -0
- package/gateway/index.ts +2 -0
- package/model-gateway-setup.ts +22 -4
- package/package.json +1 -1
package/config-setup.ts
CHANGED
|
@@ -44,8 +44,39 @@ export interface ConfigPluginConfig {
|
|
|
44
44
|
posthogHost?: string
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
48
|
-
return
|
|
47
|
+
function asObj(value: unknown): Record<string, unknown> {
|
|
48
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
49
|
+
? (value as Record<string, unknown>)
|
|
50
|
+
: {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readFilePluginConfig(config: Record<string, unknown>): ConfigPluginConfig {
|
|
54
|
+
const plugins = asObj(config.plugins)
|
|
55
|
+
const entries = asObj(plugins.entries)
|
|
56
|
+
const raw = asObj(entries['clawly-plugins'])
|
|
57
|
+
const fileConfig = raw?.config
|
|
58
|
+
return fileConfig && typeof fileConfig === 'object' && !Array.isArray(fileConfig)
|
|
59
|
+
? (fileConfig as ConfigPluginConfig)
|
|
60
|
+
: {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mergeDefinedPluginConfig(
|
|
64
|
+
fileConfig: ConfigPluginConfig,
|
|
65
|
+
runtimeConfig: ConfigPluginConfig,
|
|
66
|
+
): ConfigPluginConfig {
|
|
67
|
+
const merged = {...fileConfig}
|
|
68
|
+
for (const [key, value] of Object.entries(runtimeConfig)) {
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
merged[key as keyof ConfigPluginConfig] = value
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return merged
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toPC(api: PluginApi, config: Record<string, unknown>): ConfigPluginConfig {
|
|
77
|
+
const fileConfig = readFilePluginConfig(config)
|
|
78
|
+
const runtimeConfig = (api.pluginConfig ?? {}) as ConfigPluginConfig
|
|
79
|
+
return mergeDefinedPluginConfig(fileConfig, runtimeConfig)
|
|
49
80
|
}
|
|
50
81
|
|
|
51
82
|
const DEFAULT_MODEL = `${PROVIDER_NAME}/anthropic/claude-sonnet-4.6`
|
|
@@ -351,6 +382,58 @@ export function patchWebSearch(config: Record<string, unknown>, pc: ConfigPlugin
|
|
|
351
382
|
return dirty
|
|
352
383
|
}
|
|
353
384
|
|
|
385
|
+
export function patchMemorySearch(
|
|
386
|
+
config: Record<string, unknown>,
|
|
387
|
+
pc: ConfigPluginConfig,
|
|
388
|
+
): boolean {
|
|
389
|
+
if (!pc.modelGatewayBaseUrl || !pc.modelGatewayToken) return false
|
|
390
|
+
|
|
391
|
+
let dirty = false
|
|
392
|
+
const agents = (config.agents ?? {}) as Record<string, unknown>
|
|
393
|
+
const defaults = (agents.defaults ?? {}) as Record<string, unknown>
|
|
394
|
+
const ms = (defaults.memorySearch ?? {}) as Record<string, unknown>
|
|
395
|
+
const remote = (ms.remote ?? {}) as Record<string, unknown>
|
|
396
|
+
const batch = (remote.batch ?? {}) as Record<string, unknown>
|
|
397
|
+
|
|
398
|
+
// Provider: enforce (proxy mimics OpenAI API)
|
|
399
|
+
if (ms.provider !== 'openai') {
|
|
400
|
+
ms.provider = 'openai'
|
|
401
|
+
dirty = true
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Model: set-if-missing
|
|
405
|
+
if (ms.model === undefined) {
|
|
406
|
+
ms.model = 'text-embedding-3-small'
|
|
407
|
+
dirty = true
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Remote credentials: enforce
|
|
411
|
+
if (remote.baseUrl !== pc.modelGatewayBaseUrl) {
|
|
412
|
+
remote.baseUrl = pc.modelGatewayBaseUrl
|
|
413
|
+
dirty = true
|
|
414
|
+
}
|
|
415
|
+
if (remote.apiKey !== pc.modelGatewayToken) {
|
|
416
|
+
remote.apiKey = pc.modelGatewayToken
|
|
417
|
+
dirty = true
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Batch API not supported through proxy: enforce disabled
|
|
421
|
+
if (batch.enabled !== false) {
|
|
422
|
+
batch.enabled = false
|
|
423
|
+
dirty = true
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (dirty) {
|
|
427
|
+
remote.batch = batch
|
|
428
|
+
ms.remote = remote
|
|
429
|
+
defaults.memorySearch = ms
|
|
430
|
+
agents.defaults = defaults
|
|
431
|
+
config.agents = agents
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return dirty
|
|
435
|
+
}
|
|
436
|
+
|
|
354
437
|
export function patchSession(config: Record<string, unknown>): boolean {
|
|
355
438
|
let dirty = false
|
|
356
439
|
|
|
@@ -385,12 +468,6 @@ export function patchSession(config: Record<string, unknown>): boolean {
|
|
|
385
468
|
const PLUGIN_ID = 'clawly-plugins'
|
|
386
469
|
const NPM_PKG_NAME = '@2en/clawly-plugins'
|
|
387
470
|
|
|
388
|
-
function asObj(value: unknown): Record<string, unknown> {
|
|
389
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
390
|
-
? (value as Record<string, unknown>)
|
|
391
|
-
: {}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
471
|
/**
|
|
395
472
|
* Self-healing: reconstruct missing `plugins.installs.clawly-plugins` record.
|
|
396
473
|
* Older provisioned sprites had this record destroyed by a full-overwrite bug
|
|
@@ -470,7 +547,8 @@ function reconcileRuntimeConfig(
|
|
|
470
547
|
dirty = patchSession(config) || dirty
|
|
471
548
|
dirty = patchTts(config, pc) || dirty
|
|
472
549
|
dirty = patchWebSearch(config, pc) || dirty
|
|
473
|
-
dirty =
|
|
550
|
+
dirty = patchMemorySearch(config, pc) || dirty
|
|
551
|
+
dirty = patchModelGateway(config, api, pc) || dirty
|
|
474
552
|
return dirty
|
|
475
553
|
}
|
|
476
554
|
|
|
@@ -487,7 +565,7 @@ export function setupConfig(api: PluginApi): void {
|
|
|
487
565
|
|
|
488
566
|
const configPath = path.join(stateDir, 'openclaw.json')
|
|
489
567
|
const config = readOpenclawConfig(configPath)
|
|
490
|
-
const pc = toPC(api)
|
|
568
|
+
const pc = toPC(api, config)
|
|
491
569
|
|
|
492
570
|
let dirty = false
|
|
493
571
|
dirty = repairLegacyProvisionState(api, config, stateDir) || dirty
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone sync RPC: writes agents.defaults.userTimezone to openclaw.json
|
|
3
|
+
* without triggering a gateway restart.
|
|
4
|
+
*
|
|
5
|
+
* Methods:
|
|
6
|
+
* - clawly.config.setTimezone({ timezone }) → { changed, timezone }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
|
|
11
|
+
import type {PluginApi} from '../types'
|
|
12
|
+
import {readOpenclawConfig, writeOpenclawConfig} from '../model-gateway-setup'
|
|
13
|
+
|
|
14
|
+
export function registerConfigTimezone(api: PluginApi) {
|
|
15
|
+
api.registerGatewayMethod('clawly.config.setTimezone', async ({params, respond}) => {
|
|
16
|
+
const timezone = typeof params.timezone === 'string' ? params.timezone : ''
|
|
17
|
+
if (!timezone) {
|
|
18
|
+
respond(true, {changed: false, timezone: '', error: 'Missing timezone param'})
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const stateDir = api.runtime.state.resolveStateDir()
|
|
23
|
+
if (!stateDir) {
|
|
24
|
+
respond(true, {changed: false, timezone, error: 'Cannot resolve state dir'})
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const configPath = path.join(stateDir, 'openclaw.json')
|
|
29
|
+
const config = readOpenclawConfig(configPath)
|
|
30
|
+
|
|
31
|
+
const agents = (config.agents ?? {}) as Record<string, unknown>
|
|
32
|
+
const defaults = (agents.defaults ?? {}) as Record<string, unknown>
|
|
33
|
+
const current = defaults.userTimezone
|
|
34
|
+
|
|
35
|
+
if (current === timezone) {
|
|
36
|
+
respond(true, {changed: false, timezone})
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
defaults.userTimezone = timezone
|
|
41
|
+
agents.defaults = defaults
|
|
42
|
+
config.agents = agents
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
writeOpenclawConfig(configPath, config)
|
|
46
|
+
api.logger.info(`config-timezone: set userTimezone to ${timezone}`)
|
|
47
|
+
respond(true, {changed: true, timezone})
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
50
|
+
api.logger.error(`config-timezone: write failed — ${msg}`)
|
|
51
|
+
respond(true, {changed: false, timezone, error: `Write failed: ${msg}`})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
api.logger.info('config-timezone: registered clawly.config.setTimezone')
|
|
56
|
+
}
|
package/gateway/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {registerAgentSend} from './agent'
|
|
|
3
3
|
import {registerAnalytics} from './analytics'
|
|
4
4
|
import {registerClawhub2gateway} from './clawhub2gateway'
|
|
5
5
|
import {registerConfigRepair} from './config-repair'
|
|
6
|
+
import {registerConfigTimezone} from './config-timezone'
|
|
6
7
|
|
|
7
8
|
import {registerSessionSanitize} from './session-sanitize'
|
|
8
9
|
import {registerCronDelivery} from './cron-delivery'
|
|
@@ -56,6 +57,7 @@ export function registerGateway(api: PluginApi) {
|
|
|
56
57
|
registerCronTelemetry(api)
|
|
57
58
|
registerAnalytics(api)
|
|
58
59
|
registerConfigRepair(api)
|
|
60
|
+
registerConfigTimezone(api)
|
|
59
61
|
registerSessionSanitize(api)
|
|
60
62
|
registerPairing(api)
|
|
61
63
|
registerVersion(api)
|
package/model-gateway-setup.ts
CHANGED
|
@@ -13,6 +13,10 @@ import path from 'node:path'
|
|
|
13
13
|
import type {PluginApi} from './index'
|
|
14
14
|
|
|
15
15
|
export const PROVIDER_NAME = 'clawly-model-gateway'
|
|
16
|
+
type ModelGatewayConfigInputs = {
|
|
17
|
+
modelGatewayBaseUrl?: string
|
|
18
|
+
modelGatewayToken?: string
|
|
19
|
+
}
|
|
16
20
|
|
|
17
21
|
/** Additional models available through the model gateway (beyond env-configured defaults). */
|
|
18
22
|
export const EXTRA_GATEWAY_MODELS: Array<{
|
|
@@ -83,7 +87,17 @@ export function writeOpenclawConfig(configPath: string, config: Record<string, u
|
|
|
83
87
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n')
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
export function patchModelGateway(
|
|
90
|
+
export function patchModelGateway(
|
|
91
|
+
config: Record<string, unknown>,
|
|
92
|
+
api: PluginApi,
|
|
93
|
+
inputs?: ModelGatewayConfigInputs,
|
|
94
|
+
): boolean {
|
|
95
|
+
const cfg =
|
|
96
|
+
inputs ??
|
|
97
|
+
(api.pluginConfig as Record<string, unknown> | undefined as
|
|
98
|
+
| ModelGatewayConfigInputs
|
|
99
|
+
| undefined)
|
|
100
|
+
|
|
87
101
|
// If provider already exists, check if extra models or aliases need updating.
|
|
88
102
|
// This runs before the credentials check because provisioned sprites have
|
|
89
103
|
// credentials in openclaw.json directly, not in pluginConfig.
|
|
@@ -116,7 +130,6 @@ export function patchModelGateway(config: Record<string, unknown>, api: PluginAp
|
|
|
116
130
|
|
|
117
131
|
// Backfill pluginConfig from existing provider credentials (legacy sprites
|
|
118
132
|
// provisioned before plugin config was written during configure phase).
|
|
119
|
-
const cfg = api.pluginConfig as Record<string, unknown> | undefined
|
|
120
133
|
if (
|
|
121
134
|
existingProvider.baseUrl &&
|
|
122
135
|
existingProvider.apiKey &&
|
|
@@ -149,7 +162,6 @@ export function patchModelGateway(config: Record<string, unknown>, api: PluginAp
|
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
// No existing provider — need pluginConfig credentials to create one
|
|
152
|
-
const cfg = api.pluginConfig as Record<string, unknown> | undefined
|
|
153
165
|
const baseUrl =
|
|
154
166
|
typeof cfg?.modelGatewayBaseUrl === 'string' ? cfg.modelGatewayBaseUrl.replace(/\/$/, '') : ''
|
|
155
167
|
const token = typeof cfg?.modelGatewayToken === 'string' ? cfg.modelGatewayToken : ''
|
|
@@ -212,7 +224,13 @@ export function patchModelGateway(config: Record<string, unknown>, api: PluginAp
|
|
|
212
224
|
return true
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Standalone wrapper — reads config, patches, writes. Used by tests.
|
|
229
|
+
*
|
|
230
|
+
* This helper intentionally does not apply the file-backed pluginConfig fallback
|
|
231
|
+
* from setupConfig(); production startup should go through the full runtime
|
|
232
|
+
* reconcile path there.
|
|
233
|
+
*/
|
|
216
234
|
export function setupModelGateway(api: PluginApi): void {
|
|
217
235
|
const stateDir = api.runtime.state.resolveStateDir()
|
|
218
236
|
if (!stateDir) {
|