@2en/clawly-plugins 1.30.0 → 1.31.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 +71 -123
- package/package.json +1 -1
- package/tools/clawly-search.test.ts +1 -1
- package/tools/create-search-tool.ts +1 -1
package/config-setup.ts
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
ENV_KEY_API_KEY,
|
|
25
25
|
ENV_KEY_BASE,
|
|
26
26
|
PROVIDER_NAME,
|
|
27
|
-
patchModelGateway,
|
|
28
27
|
readOpenclawConfig,
|
|
29
28
|
stripPathname,
|
|
30
29
|
writeOpenclawConfig,
|
|
@@ -340,48 +339,82 @@ export function patchSession(config: OpenClawConfig): boolean {
|
|
|
340
339
|
return dirty
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
const
|
|
342
|
+
const DEFAULTS_PATH = './extensions/clawly-plugins/clawly-config-defaults.json5'
|
|
343
|
+
const LEGACY_INCLUDE_PATH = DEFAULTS_PATH
|
|
344
344
|
|
|
345
345
|
/**
|
|
346
|
-
*
|
|
347
|
-
* Returns true if
|
|
346
|
+
* Remove legacy `$include` reference to our defaults file.
|
|
347
|
+
* Returns true if config was modified.
|
|
348
348
|
*/
|
|
349
|
-
export function
|
|
349
|
+
export function removeInclude(config: OpenClawConfig & Record<string, unknown>): boolean {
|
|
350
350
|
const existing = config.$include
|
|
351
|
-
|
|
351
|
+
if (existing === undefined) return false
|
|
352
352
|
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
353
|
+
if (typeof existing === 'string') {
|
|
354
|
+
if (existing === LEGACY_INCLUDE_PATH) {
|
|
355
|
+
delete config.$include
|
|
356
|
+
return true
|
|
357
|
+
}
|
|
358
|
+
return false
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
if (
|
|
361
|
+
if (Array.isArray(existing)) {
|
|
362
|
+
const includes = existing as string[]
|
|
363
|
+
const idx = includes.indexOf(LEGACY_INCLUDE_PATH)
|
|
364
|
+
if (idx === -1) return false
|
|
365
|
+
includes.splice(idx, 1)
|
|
366
|
+
if (includes.length === 0) {
|
|
367
|
+
delete config.$include
|
|
368
|
+
} else if (includes.length === 1) {
|
|
369
|
+
config.$include = includes[0]
|
|
370
|
+
}
|
|
371
|
+
return true
|
|
372
|
+
}
|
|
362
373
|
|
|
363
|
-
|
|
364
|
-
config.$include = includes.length === 1 ? includes[0] : includes
|
|
365
|
-
return true
|
|
374
|
+
return false
|
|
366
375
|
}
|
|
367
376
|
|
|
368
377
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
378
|
+
* Deep-merge defaults into config. Config values take precedence;
|
|
379
|
+
* defaults only fill in missing keys. Arrays are not merged — config wins.
|
|
380
|
+
* Returns true if any key was added.
|
|
372
381
|
*/
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
382
|
+
export function deepMergeDefaults(
|
|
383
|
+
config: Record<string, unknown>,
|
|
384
|
+
defaults: Record<string, unknown>,
|
|
385
|
+
): boolean {
|
|
386
|
+
let merged = false
|
|
387
|
+
for (const key of Object.keys(defaults)) {
|
|
388
|
+
const defaultVal = defaults[key]
|
|
389
|
+
const configVal = config[key]
|
|
390
|
+
|
|
391
|
+
if (configVal === undefined) {
|
|
392
|
+
config[key] = defaultVal
|
|
393
|
+
merged = true
|
|
394
|
+
continue
|
|
381
395
|
}
|
|
382
|
-
|
|
383
|
-
//
|
|
396
|
+
|
|
397
|
+
// Both are plain objects → recurse
|
|
398
|
+
if (
|
|
399
|
+
defaultVal !== null &&
|
|
400
|
+
typeof defaultVal === 'object' &&
|
|
401
|
+
!Array.isArray(defaultVal) &&
|
|
402
|
+
configVal !== null &&
|
|
403
|
+
typeof configVal === 'object' &&
|
|
404
|
+
!Array.isArray(configVal)
|
|
405
|
+
) {
|
|
406
|
+
if (
|
|
407
|
+
deepMergeDefaults(
|
|
408
|
+
configVal as Record<string, unknown>,
|
|
409
|
+
defaultVal as Record<string, unknown>,
|
|
410
|
+
)
|
|
411
|
+
) {
|
|
412
|
+
merged = true
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// For arrays and primitives: config wins, do nothing
|
|
384
416
|
}
|
|
417
|
+
return merged
|
|
385
418
|
}
|
|
386
419
|
|
|
387
420
|
const PLUGIN_ID = 'clawly-plugins'
|
|
@@ -450,89 +483,12 @@ function repairLegacyProvisionState(api: PluginApi, config: OpenClawConfig, stat
|
|
|
450
483
|
return installRecordPatched
|
|
451
484
|
}
|
|
452
485
|
|
|
453
|
-
/**
|
|
454
|
-
* Remove fields from config that are identical to the included defaults.
|
|
455
|
-
* Since `$include` deep-merges with main config winning, residual values
|
|
456
|
-
* from old JS set-if-missing code "shadow" the json5 defaults and prevent
|
|
457
|
-
* future default updates from taking effect.
|
|
458
|
-
*
|
|
459
|
-
* Only prunes leaf values that exactly match.
|
|
460
|
-
* Returns true if any field was deleted.
|
|
461
|
-
*/
|
|
462
|
-
function stableStringify(v: unknown): string {
|
|
463
|
-
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
464
|
-
const obj = v as Record<string, unknown>
|
|
465
|
-
return `{${Object.keys(obj)
|
|
466
|
-
.sort()
|
|
467
|
-
.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
|
|
468
|
-
.join(',')}}`
|
|
469
|
-
}
|
|
470
|
-
return JSON.stringify(v)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export function pruneIncludedDefaults(
|
|
474
|
-
config: Record<string, unknown>,
|
|
475
|
-
defaults: Record<string, unknown>,
|
|
476
|
-
): boolean {
|
|
477
|
-
let pruned = false
|
|
478
|
-
|
|
479
|
-
function walk(defaultsNode: Record<string, unknown>, configNode: Record<string, unknown>): void {
|
|
480
|
-
for (const key of Object.keys(defaultsNode)) {
|
|
481
|
-
const defaultVal = defaultsNode[key]
|
|
482
|
-
const configVal = configNode[key]
|
|
483
|
-
if (configVal === undefined) continue
|
|
484
|
-
|
|
485
|
-
// Both are plain objects → recurse
|
|
486
|
-
if (
|
|
487
|
-
defaultVal !== null &&
|
|
488
|
-
typeof defaultVal === 'object' &&
|
|
489
|
-
!Array.isArray(defaultVal) &&
|
|
490
|
-
configVal !== null &&
|
|
491
|
-
typeof configVal === 'object' &&
|
|
492
|
-
!Array.isArray(configVal)
|
|
493
|
-
) {
|
|
494
|
-
walk(defaultVal as Record<string, unknown>, configVal as Record<string, unknown>)
|
|
495
|
-
// Clean up empty parent objects left behind after pruning
|
|
496
|
-
if (Object.keys(configVal as Record<string, unknown>).length === 0) {
|
|
497
|
-
delete configNode[key]
|
|
498
|
-
pruned = true
|
|
499
|
-
}
|
|
500
|
-
continue
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Both are non-empty arrays → remove config elements that exist in defaults
|
|
504
|
-
if (Array.isArray(defaultVal) && defaultVal.length > 0 && Array.isArray(configVal)) {
|
|
505
|
-
const defaultSet = new Set(defaultVal.map((v) => stableStringify(v)))
|
|
506
|
-
const filtered = configVal.filter((v) => !defaultSet.has(stableStringify(v)))
|
|
507
|
-
if (filtered.length !== configVal.length) {
|
|
508
|
-
if (filtered.length === 0) {
|
|
509
|
-
delete configNode[key]
|
|
510
|
-
} else {
|
|
511
|
-
configNode[key] = filtered
|
|
512
|
-
}
|
|
513
|
-
pruned = true
|
|
514
|
-
}
|
|
515
|
-
continue
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Leaf comparison: use stableStringify for key-order-insensitive deep equality
|
|
519
|
-
if (stableStringify(configVal) === stableStringify(defaultVal)) {
|
|
520
|
-
delete configNode[key]
|
|
521
|
-
pruned = true
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
walk(defaults, config)
|
|
527
|
-
return pruned
|
|
528
|
-
}
|
|
529
|
-
|
|
530
486
|
/**
|
|
531
487
|
* Load and parse clawly-config-defaults.json5 from the plugin install directory.
|
|
532
488
|
* Uses the bundled `json5` package (declared in package.json).
|
|
533
489
|
*/
|
|
534
|
-
function
|
|
535
|
-
const defaultsPath = path.join(stateDir,
|
|
490
|
+
function loadDefaults(stateDir: string): Record<string, unknown> | null {
|
|
491
|
+
const defaultsPath = path.join(stateDir, DEFAULTS_PATH)
|
|
536
492
|
try {
|
|
537
493
|
const raw = fs.readFileSync(defaultsPath, 'utf-8')
|
|
538
494
|
return JSON5.parse(raw) as Record<string, unknown>
|
|
@@ -565,13 +521,12 @@ function reconcileRuntimeConfig(
|
|
|
565
521
|
dirty = patchGateway(config) || dirty
|
|
566
522
|
dirty = patchBrowser(config) || dirty
|
|
567
523
|
dirty = patchSession(config) || dirty
|
|
568
|
-
dirty = patchModelGateway(config, api) || dirty
|
|
569
524
|
|
|
570
|
-
const defaults =
|
|
525
|
+
const defaults = loadDefaults(stateDir)
|
|
571
526
|
if (defaults) {
|
|
572
|
-
dirty =
|
|
527
|
+
dirty = deepMergeDefaults(config, defaults) || dirty
|
|
573
528
|
} else {
|
|
574
|
-
api.logger.warn('Config setup: failed to load
|
|
529
|
+
api.logger.warn('Config setup: failed to load defaults json5, skipping merge.')
|
|
575
530
|
}
|
|
576
531
|
|
|
577
532
|
return dirty
|
|
@@ -588,22 +543,15 @@ export function setupConfig(api: PluginApi): void {
|
|
|
588
543
|
return
|
|
589
544
|
}
|
|
590
545
|
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
// runtime — resolved + SecretRef resolution, cached in-memory snapshot
|
|
595
|
-
//
|
|
596
|
-
// We need parsed: patchers mutate raw fields and write back via
|
|
597
|
-
// writeConfigFile, which diffs against the runtime snapshot and projects
|
|
598
|
-
// changes onto the source file. Using resolved/runtime would inline
|
|
599
|
-
// $include defaults into openclaw.json, defeating the json5 migration.
|
|
546
|
+
// Read the raw parsed config from openclaw.json (no env expansion).
|
|
547
|
+
// Patchers mutate fields in place; defaults from json5 are deep-merged
|
|
548
|
+
// to fill missing keys. The result is written back as the full config.
|
|
600
549
|
const configPath = path.join(stateDir, 'openclaw.json')
|
|
601
550
|
const config = readOpenclawConfig(configPath)
|
|
602
551
|
const pc = toPCMerged(api, config)
|
|
603
552
|
|
|
604
553
|
let dirty = false
|
|
605
|
-
dirty =
|
|
606
|
-
hardenIncludePermissions(stateDir, api)
|
|
554
|
+
dirty = removeInclude(config) || dirty
|
|
607
555
|
dirty = repairLegacyProvisionState(api, config, stateDir) || dirty
|
|
608
556
|
dirty = reconcileRuntimeConfig(api, config, pc, stateDir) || dirty
|
|
609
557
|
|
package/package.json
CHANGED
|
@@ -289,7 +289,7 @@ describe('clawly_kimi_search', () => {
|
|
|
289
289
|
expect(call.body?.model).toBe('kimi-k2-turbo-preview')
|
|
290
290
|
expect(call.body?.stream).toBe(false)
|
|
291
291
|
expect(call.body?.messages).toEqual([{role: 'user', content: 'Chinese news'}])
|
|
292
|
-
expect(call.body?.tools).toEqual([{type: 'builtin_function', function: {name: 'web_search'}}])
|
|
292
|
+
expect(call.body?.tools).toEqual([{type: 'builtin_function', function: {name: '$web_search'}}])
|
|
293
293
|
|
|
294
294
|
expect(res.answer).toBe('Kimi answer with [source](https://example.com/page)')
|
|
295
295
|
expect(res.citations).toEqual(['https://example.com/page'])
|
|
@@ -53,7 +53,7 @@ export function buildKimiBody(model: string, query: string): Record<string, unkn
|
|
|
53
53
|
model,
|
|
54
54
|
stream: false,
|
|
55
55
|
messages: [{role: 'user', content: query}],
|
|
56
|
-
tools: [{type: 'builtin_function', function: {name: 'web_search'}}],
|
|
56
|
+
tools: [{type: 'builtin_function', function: {name: '$web_search'}}],
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|