@2en/clawly-plugins 1.30.0-beta.5 → 1.30.0-beta.6

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.
@@ -179,9 +179,23 @@
179
179
  },
180
180
  },
181
181
 
182
+ // gateway.reload — use "hot" mode to prevent spurious SIGUSR1 restarts.
183
+ // config-setup writes restart-requiring keys (commands.*, gateway.*, env.*)
184
+ // after the gateway has already captured its startup config snapshot.
185
+ // In "hybrid" (default) mode, any subsequent config file write triggers the
186
+ // file watcher to diff against the stale snapshot, discover the config-setup
187
+ // changes as "new", and issue SIGUSR1. "hot" mode makes the watcher ignore
188
+ // restart-requiring diffs — only hot-reloadable changes (heartbeat, cron,
189
+ // hooks, browser) are applied. Direct SIGUSR1 from explicit restart commands
190
+ // and plugin auto-update is unaffected. If you manually edit a restart-
191
+ // requiring field in openclaw.json, run `openclaw restart` to apply it.
192
+ //
182
193
  // gateway.nodes — allowlist dangerous node commands so the AI agent can
183
194
  // invoke them on connected Clawly nodes (browser, reminders, calendar, device).
184
195
  gateway: {
196
+ reload: {
197
+ mode: "hot",
198
+ },
185
199
  nodes: {
186
200
  allowCommands: [
187
201
  // Browser commands (Mac nodes)
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Model switch RPC: writes agents.defaults.model.primary to openclaw.json
3
+ * without triggering a gateway restart.
4
+ *
5
+ * OpenClaw reads agents.defaults.model.primary dynamically on each chat
6
+ * request (loadConfig() has a ~200ms cache). A disk write takes effect
7
+ * within that window — no SIGUSR1 needed.
8
+ *
9
+ * Uses the runtime config APIs (loadConfig + writeConfigFile) for atomic
10
+ * writes, env var preservation, and config validation.
11
+ *
12
+ * Methods:
13
+ * - clawly.config.setModel({ model }) → { changed, model }
14
+ */
15
+
16
+ import type {PluginApi} from '../types'
17
+ import type {OpenClawConfig} from '../types/openclaw'
18
+
19
+ export function registerConfigModel(api: PluginApi) {
20
+ api.registerGatewayMethod('clawly.config.setModel', async ({params, respond}) => {
21
+ const model = typeof params.model === 'string' ? params.model : ''
22
+ if (!model) {
23
+ respond(true, {changed: false, model: '', error: 'Missing model param'})
24
+ return
25
+ }
26
+
27
+ let config: OpenClawConfig
28
+ try {
29
+ config = {...(api.runtime.config.loadConfig() as OpenClawConfig)}
30
+ } catch (err) {
31
+ const msg = err instanceof Error ? err.message : String(err)
32
+ respond(true, {changed: false, model, error: `Load failed: ${msg}`})
33
+ return
34
+ }
35
+
36
+ const current = (config.agents as Record<string, unknown> | undefined)?.defaults as
37
+ | Record<string, unknown>
38
+ | undefined
39
+ const currentModel = (current?.model as Record<string, unknown> | undefined)?.primary
40
+
41
+ if (currentModel === model) {
42
+ respond(true, {changed: false, model})
43
+ return
44
+ }
45
+
46
+ // Shallow-copy nested objects to avoid polluting the loadConfig() cache
47
+ // if writeConfigFile fails below.
48
+ const agents = {...((config.agents ?? {}) as Record<string, unknown>)}
49
+ const defaults = {...((agents.defaults ?? {}) as Record<string, unknown>)}
50
+ const modelObj = {...((defaults.model ?? {}) as Record<string, unknown>)}
51
+ modelObj.primary = model
52
+ defaults.model = modelObj
53
+ agents.defaults = defaults
54
+ config.agents = agents
55
+
56
+ try {
57
+ await api.runtime.config.writeConfigFile(config)
58
+ api.logger.info(`config-model: set model.primary to ${model}`)
59
+ respond(true, {changed: true, model})
60
+ } catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err)
62
+ api.logger.error(`config-model: write failed — ${msg}`)
63
+ respond(true, {changed: false, model, error: `Write failed: ${msg}`})
64
+ }
65
+ })
66
+
67
+ api.logger.info('config-model: registered clawly.config.setModel')
68
+ }
@@ -2,14 +2,15 @@
2
2
  * Timezone sync RPC: writes agents.defaults.userTimezone to openclaw.json
3
3
  * without triggering a gateway restart.
4
4
  *
5
+ * Uses the runtime config APIs (loadConfig + writeConfigFile) for atomic
6
+ * writes, env var preservation, and config validation.
7
+ *
5
8
  * Methods:
6
9
  * - clawly.config.setTimezone({ timezone }) → { changed, timezone }
7
10
  */
8
11
 
9
- import path from 'node:path'
10
-
11
12
  import type {PluginApi} from '../types'
12
- import {readOpenclawConfig, writeOpenclawConfig} from '../model-gateway-setup'
13
+ import type {OpenClawConfig} from '../types/openclaw'
13
14
 
14
15
  export function registerConfigTimezone(api: PluginApi) {
15
16
  api.registerGatewayMethod('clawly.config.setTimezone', async ({params, respond}) => {
@@ -19,30 +20,34 @@ export function registerConfigTimezone(api: PluginApi) {
19
20
  return
20
21
  }
21
22
 
22
- const stateDir = api.runtime.state.resolveStateDir()
23
- if (!stateDir) {
24
- respond(true, {changed: false, timezone, error: 'Cannot resolve state dir'})
23
+ let config: OpenClawConfig
24
+ try {
25
+ config = {...(api.runtime.config.loadConfig() as OpenClawConfig)}
26
+ } catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err)
28
+ respond(true, {changed: false, timezone, error: `Load failed: ${msg}`})
25
29
  return
26
30
  }
27
31
 
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
32
+ const currentDefaults = (config.agents as Record<string, unknown> | undefined)?.defaults as
33
+ | Record<string, unknown>
34
+ | undefined
34
35
 
35
- if (current === timezone) {
36
+ if (currentDefaults?.userTimezone === timezone) {
36
37
  respond(true, {changed: false, timezone})
37
38
  return
38
39
  }
39
40
 
41
+ // Shallow-copy nested objects to avoid polluting the loadConfig() cache
42
+ // if writeConfigFile fails below.
43
+ const agents = {...((config.agents ?? {}) as Record<string, unknown>)}
44
+ const defaults = {...((agents.defaults ?? {}) as Record<string, unknown>)}
40
45
  defaults.userTimezone = timezone
41
46
  agents.defaults = defaults
42
47
  config.agents = agents
43
48
 
44
49
  try {
45
- writeOpenclawConfig(configPath, config)
50
+ await api.runtime.config.writeConfigFile(config)
46
51
  api.logger.info(`config-timezone: set userTimezone to ${timezone}`)
47
52
  respond(true, {changed: true, timezone})
48
53
  } catch (err) {
package/gateway/index.ts CHANGED
@@ -4,6 +4,7 @@ import {registerCalendarNative} from './calendar-native'
4
4
  import {registerAnalytics} from './analytics'
5
5
  import {registerAudit} from './audit'
6
6
  import {registerClawhub2gateway} from './clawhub2gateway'
7
+ import {registerConfigModel} from './config-model'
7
8
  import {registerConfigRepair} from './config-repair'
8
9
  import {registerConfigTimezone} from './config-timezone'
9
10
  import {registerCronDelivery} from './cron-delivery'
@@ -57,6 +58,7 @@ export function registerGateway(api: PluginApi) {
57
58
  registerCronTelemetry(api)
58
59
  registerMessageLog(api)
59
60
  registerAnalytics(api)
61
+ registerConfigModel(api)
60
62
  registerConfigRepair(api)
61
63
  registerConfigTimezone(api)
62
64
  registerSessionSanitize(api)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.30.0-beta.5",
3
+ "version": "1.30.0-beta.6",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {