@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 CHANGED
@@ -44,8 +44,39 @@ export interface ConfigPluginConfig {
44
44
  posthogHost?: string
45
45
  }
46
46
 
47
- function toPC(api: PluginApi): ConfigPluginConfig {
48
- return (api.pluginConfig ?? {}) as ConfigPluginConfig
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 = patchModelGateway(config, api) || 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)
@@ -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(config: Record<string, unknown>, api: PluginApi): boolean {
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
- /** Standalone wrapper — reads config, patches, writes. Used by tests. */
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.26.1-beta.0",
3
+ "version": "1.26.1-beta.1",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {