@2en/clawly-plugins 1.30.0-beta.7 → 1.30.0-beta.9

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.
@@ -134,7 +134,7 @@
134
134
  mode: "cache-ttl",
135
135
  },
136
136
  heartbeat: {
137
- model: "clawly-model-gateway/moonshotai/kimi-k2.5",
137
+ model: "clawly-model-gateway/auto",
138
138
  lightContext: true,
139
139
  },
140
140
  memorySearch: {
package/config-setup.ts CHANGED
@@ -611,14 +611,12 @@ export function setupConfig(api: PluginApi): void {
611
611
  try {
612
612
  writeOpenclawConfig(configPath, config)
613
613
  api.logger.info('Config setup: patched openclaw.json.')
614
- // Refresh the gateway's in-memory runtime config snapshot.
615
- // The sync write above updates the file on disk, but the gateway
616
- // caches config via runtimeConfigSnapshot (set during startup by
617
- // the secrets system). Without this refresh, loadConfig() keeps
618
- // returning the stale pre-patch config until the next restart.
619
- void api.runtime.config.writeConfigFile(config).catch((err) => {
620
- api.logger.warn(`Config setup: runtime snapshot refresh failed: ${(err as Error).message}`)
621
- })
614
+ // NOTE: We intentionally do NOT call api.runtime.config.writeConfigFile()
615
+ // here. The raw config (from readOpenclawConfig / JSON.parse) lacks
616
+ // $include-resolved fields (e.g. models array) and always fails
617
+ // validation. The raw disk write above is sufficient — the first
618
+ // successful writeConfigFile call from an RPC (like setTimezone)
619
+ // will refresh the runtime snapshot.
622
620
  } catch (err) {
623
621
  api.logger.error(`Config setup failed: ${(err as Error).message}`)
624
622
  }
@@ -15,6 +15,7 @@
15
15
 
16
16
  import type {PluginApi} from '../types'
17
17
  import type {OpenClawConfig} from '../types/openclaw'
18
+ import {backfillDiskConfig} from '../model-gateway-setup'
18
19
 
19
20
  export function registerConfigModel(api: PluginApi) {
20
21
  api.registerGatewayMethod('clawly.config.setModel', async ({params, respond}) => {
@@ -53,8 +54,13 @@ export function registerConfigModel(api: PluginApi) {
53
54
  agents.defaults = defaults
54
55
  config.agents = agents
55
56
 
57
+ // Backfill fields written by setupConfig (which writes directly to disk)
58
+ // so writeConfigFile's merge-patch doesn't revert them.
59
+ const stateDir = api.runtime.state.resolveStateDir()
60
+ const configToWrite = stateDir ? backfillDiskConfig(stateDir, config) : config
61
+
56
62
  try {
57
- await api.runtime.config.writeConfigFile(config)
63
+ await api.runtime.config.writeConfigFile(configToWrite)
58
64
  api.logger.info(`config-model: set model.primary to ${model}`)
59
65
  respond(true, {changed: true, model})
60
66
  } catch (err) {
@@ -11,6 +11,7 @@
11
11
 
12
12
  import type {PluginApi} from '../types'
13
13
  import type {OpenClawConfig} from '../types/openclaw'
14
+ import {backfillDiskConfig} from '../model-gateway-setup'
14
15
 
15
16
  export function registerConfigTimezone(api: PluginApi) {
16
17
  api.registerGatewayMethod('clawly.config.setTimezone', async ({params, respond}) => {
@@ -46,8 +47,13 @@ export function registerConfigTimezone(api: PluginApi) {
46
47
  agents.defaults = defaults
47
48
  config.agents = agents
48
49
 
50
+ // Backfill fields written by setupConfig (which writes directly to disk)
51
+ // so writeConfigFile's merge-patch doesn't revert them.
52
+ const stateDir = api.runtime.state.resolveStateDir()
53
+ const configToWrite = stateDir ? backfillDiskConfig(stateDir, config) : config
54
+
49
55
  try {
50
- await api.runtime.config.writeConfigFile(config)
56
+ await api.runtime.config.writeConfigFile(configToWrite)
51
57
  api.logger.info(`config-timezone: set userTimezone to ${timezone}`)
52
58
  respond(true, {changed: true, timezone})
53
59
  } catch (err) {
@@ -109,7 +109,12 @@ export function initCache(): void {
109
109
  }
110
110
 
111
111
  export function getCache(): CalendarCache | null {
112
- if (!cachedData) cachedData = loadCache()
112
+ // Always read from disk to avoid returning stale in-memory state.
113
+ // After gateway restart or ESM/CJS dual-loading, gateway methods and
114
+ // agent tools may hold separate module instances with divergent
115
+ // cachedData. Disk is the shared source of truth. The file is small
116
+ // (<10 KB) and tool calls are infrequent, so the I/O cost is negligible.
117
+ cachedData = loadCache()
113
118
  return cachedData
114
119
  }
115
120
 
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import fs from 'node:fs'
13
+ import path from 'node:path'
13
14
 
14
15
  import type {PluginApi} from './index'
15
16
  import type {OpenClawConfig} from './types/openclaw'
@@ -67,3 +68,48 @@ export function patchModelGateway(config: OpenClawConfig, _api: PluginApi): bool
67
68
 
68
69
  return dirty
69
70
  }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // backfillDiskConfig — merge setupConfig's disk writes into a loadConfig() result
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
77
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
78
+ }
79
+
80
+ /**
81
+ * Deep-merge `source` into `target`, where source wins for any key it has.
82
+ * Arrays and primitives from source overwrite target.
83
+ * Plain objects recurse.
84
+ */
85
+ function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): void {
86
+ for (const key of Object.keys(source)) {
87
+ if (key === '$include') continue // meta-directive, not a resolved config field
88
+ const sv = source[key]
89
+ const tv = target[key]
90
+ if (isPlainObject(sv) && isPlainObject(tv)) {
91
+ deepMerge(tv, sv)
92
+ } else {
93
+ target[key] = sv
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Read the raw disk config (openclaw.json) and deep-merge its fields into a
100
+ * runtime config snapshot (from `loadConfig()`). This ensures that fields
101
+ * written by `setupConfig` (which writes directly to disk) are present in the
102
+ * config object before it's passed to `writeConfigFile`.
103
+ *
104
+ * Without this, `writeConfigFile`'s inner merge-patch sees the stale runtime
105
+ * snapshot (missing setupConfig's changes) and reverts them on disk.
106
+ */
107
+ export function backfillDiskConfig(stateDir: string, config: OpenClawConfig): OpenClawConfig {
108
+ const configPath = path.join(stateDir, 'openclaw.json')
109
+ const diskConfig = readOpenclawConfig(configPath)
110
+ // Start from disk (setupConfig's fields) then overlay config (RPC handler's
111
+ // changes) so the caller's modifications win over stale disk values.
112
+ const merged = {...diskConfig} as Record<string, unknown>
113
+ deepMerge(merged, config as Record<string, unknown>)
114
+ return merged as OpenClawConfig
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.30.0-beta.7",
3
+ "version": "1.30.0-beta.9",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {