@2en/clawly-plugins 1.26.0-beta.1 → 1.26.0

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
@@ -1,17 +1,21 @@
1
1
  /**
2
- * On plugin init, patches openclaw.json to set all business config that was
3
- * previously written by provision's buildConfig().
2
+ * Reconciles runtime config owned by clawly-plugins after provision has already
3
+ * established the bootstrap baseline (plugin entry, pluginConfig inputs,
4
+ * workspace files, install metadata).
4
5
  *
5
- * Domain helpers read the pluginConfig, apply enforce or set-if-missing
6
- * semantics, and write once if anything changed. This runs before
7
- * setupModelGateway so agents.defaults.model is available for the model
8
- * provider setup.
6
+ * This file intentionally owns two narrower responsibilities:
7
+ * - repair legacy state damaged by older provision flows
8
+ * - reconcile runtime-facing config derived from pluginConfig
9
9
  *
10
- * Backward compatibility: old sprites have pluginConfig with only 4 fields
11
- * (skill/model gateway credentials). Helpers check for the new fields and
12
- * skip gracefully when absent.
10
+ * It does not try to redefine provision's startup contract. Anything that must
11
+ * exist before the first gateway boot should remain provision-owned.
12
+ *
13
+ * Backward compatibility: old sprites may have only the original gateway
14
+ * credentials in pluginConfig. Helpers check for newer fields and skip
15
+ * gracefully when absent.
13
16
  */
14
17
 
18
+ import fs from 'node:fs'
15
19
  import path from 'node:path'
16
20
 
17
21
  import type {PluginApi} from './index'
@@ -377,8 +381,100 @@ export function patchSession(config: Record<string, unknown>): boolean {
377
381
  return dirty
378
382
  }
379
383
 
384
+ const PLUGIN_ID = 'clawly-plugins'
385
+ const NPM_PKG_NAME = '@2en/clawly-plugins'
386
+
387
+ function asObj(value: unknown): Record<string, unknown> {
388
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
389
+ ? (value as Record<string, unknown>)
390
+ : {}
391
+ }
392
+
393
+ /**
394
+ * Self-healing: reconstruct missing `plugins.installs.clawly-plugins` record.
395
+ * Older provisioned sprites had this record destroyed by a full-overwrite bug
396
+ * in the provision write-plugins-config step. Without this record,
397
+ * `openclaw plugins update` cannot function.
398
+ */
399
+ export function patchInstallRecord(config: Record<string, unknown>, stateDir: string): boolean {
400
+ const plugins = asObj(config.plugins)
401
+ const installs = asObj(plugins.installs)
402
+
403
+ // Already has a valid install record — nothing to do
404
+ if (
405
+ installs[PLUGIN_ID] &&
406
+ typeof installs[PLUGIN_ID] === 'object' &&
407
+ (installs[PLUGIN_ID] as Record<string, unknown>).source &&
408
+ (installs[PLUGIN_ID] as Record<string, unknown>).spec
409
+ )
410
+ return false
411
+
412
+ // Read version from installed plugin's package.json
413
+ const installPath = path.join(stateDir, 'extensions', PLUGIN_ID)
414
+ const pkgPath = path.join(installPath, 'package.json')
415
+ let rawPkg: string
416
+ try {
417
+ rawPkg = fs.readFileSync(pkgPath, 'utf-8')
418
+ } catch {
419
+ // Plugin not installed on disk — skip
420
+ return false
421
+ }
422
+ let version: string | undefined
423
+ try {
424
+ const pkg = JSON.parse(rawPkg)
425
+ if (typeof pkg.version === 'string') version = pkg.version
426
+ } catch {
427
+ // package.json is malformed — proceed without version
428
+ }
429
+ let installedAt = new Date().toISOString()
430
+ try {
431
+ installedAt = fs.statSync(pkgPath).mtime.toISOString()
432
+ } catch {
433
+ // Fall back to repair time if file metadata is unavailable.
434
+ }
435
+
436
+ installs[PLUGIN_ID] = {
437
+ source: 'npm',
438
+ spec: version ? `${NPM_PKG_NAME}@${version}` : NPM_PKG_NAME,
439
+ installPath,
440
+ version,
441
+ installedAt,
442
+ }
443
+ plugins.installs = installs
444
+ config.plugins = plugins
445
+ return true
446
+ }
447
+
448
+ function repairLegacyProvisionState(
449
+ api: PluginApi,
450
+ config: Record<string, unknown>,
451
+ stateDir: string,
452
+ ) {
453
+ const installRecordPatched = patchInstallRecord(config, stateDir)
454
+ if (installRecordPatched) {
455
+ api.logger.warn('plugins.installs.clawly-plugins was missing — self-healed from disk.')
456
+ }
457
+ return installRecordPatched
458
+ }
459
+
460
+ function reconcileRuntimeConfig(
461
+ api: PluginApi,
462
+ config: Record<string, unknown>,
463
+ pc: ConfigPluginConfig,
464
+ ): boolean {
465
+ let dirty = false
466
+ dirty = patchAgent(config, pc) || dirty
467
+ dirty = patchGateway(config) || dirty
468
+ dirty = patchBrowser(config) || dirty
469
+ dirty = patchSession(config) || dirty
470
+ dirty = patchTts(config, pc) || dirty
471
+ dirty = patchWebSearch(config, pc) || dirty
472
+ dirty = patchModelGateway(config, api) || dirty
473
+ return dirty
474
+ }
475
+
380
476
  // ---------------------------------------------------------------------------
381
- // Entry point — single read, patch all, single write
477
+ // Entry point — single read, targeted reconcile, single write
382
478
  // ---------------------------------------------------------------------------
383
479
 
384
480
  export function setupConfig(api: PluginApi): void {
@@ -393,13 +489,8 @@ export function setupConfig(api: PluginApi): void {
393
489
  const pc = toPC(api)
394
490
 
395
491
  let dirty = false
396
- dirty = patchAgent(config, pc) || dirty
397
- dirty = patchGateway(config) || dirty
398
- dirty = patchBrowser(config) || dirty
399
- dirty = patchSession(config) || dirty
400
- dirty = patchTts(config, pc) || dirty
401
- dirty = patchWebSearch(config, pc) || dirty
402
- dirty = patchModelGateway(config, api) || dirty
492
+ dirty = repairLegacyProvisionState(api, config, stateDir) || dirty
493
+ dirty = reconcileRuntimeConfig(api, config, pc) || dirty
403
494
 
404
495
  if (!dirty) {
405
496
  api.logger.info('Config setup: no changes needed.')
@@ -1,12 +1,10 @@
1
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`
5
- * already present in the config.
2
+ * Reconciles the `clawly-model-gateway` provider in openclaw.json from
3
+ * pluginConfig inputs and the current runtime-facing agent defaults.
6
4
  *
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.
5
+ * This is a runtime reconcile path, not a provision/bootstrap contract.
6
+ * The first-boot correctness of gateway startup should not rely on this file
7
+ * write winning a race against OpenClaw's internal startup snapshot timing.
10
8
  */
11
9
 
12
10
  import fs from 'node:fs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.26.0-beta.1",
3
+ "version": "1.26.0",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {