@karmaniverous/jeeves-server-openclaw 0.7.0 → 0.7.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/dist/cli.js +99 -21
- package/dist/index.js +198 -84
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import path, { join, dirname, resolve } from 'node:path';
|
|
2
|
+
import path, { join, dirname, resolve, basename } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import fs, { existsSync, copyFileSync, writeFileSync, readFileSync, rmSync, mkdirSync, readdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
5
6
|
import require$$0$4 from 'path';
|
|
6
7
|
import require$$0$3 from 'fs';
|
|
7
8
|
import require$$0$1 from 'constants';
|
|
@@ -15,7 +16,6 @@ import require$$1 from 'node:child_process';
|
|
|
15
16
|
import process$2 from 'node:process';
|
|
16
17
|
import 'node:fs/promises';
|
|
17
18
|
import { homedir } from 'node:os';
|
|
18
|
-
import 'node:crypto';
|
|
19
19
|
|
|
20
20
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
21
21
|
|
|
@@ -15403,14 +15403,14 @@ const SECTION_ORDER = [
|
|
|
15403
15403
|
* Core library version, inlined at build time.
|
|
15404
15404
|
*
|
|
15405
15405
|
* @remarks
|
|
15406
|
-
* The `0.5.
|
|
15406
|
+
* The `0.5.2` placeholder is replaced by
|
|
15407
15407
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
15408
15408
|
* from `package.json`. This ensures the correct version survives
|
|
15409
15409
|
* when consumers bundle core into their own dist (where runtime
|
|
15410
15410
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
15411
15411
|
*/
|
|
15412
15412
|
/** The core library version from package.json (inlined at build time). */
|
|
15413
|
-
const CORE_VERSION = '0.5.
|
|
15413
|
+
const CORE_VERSION = '0.5.2';
|
|
15414
15414
|
|
|
15415
15415
|
/**
|
|
15416
15416
|
* Workspace and config root initialization.
|
|
@@ -15460,27 +15460,46 @@ const STALE_LOCK_MS = 120_000;
|
|
|
15460
15460
|
const DEFAULT_CORE_VERSION = CORE_VERSION;
|
|
15461
15461
|
/** Lock retry options. */
|
|
15462
15462
|
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
15463
|
+
/** Maximum rename retry attempts on EPERM. */
|
|
15464
|
+
const ATOMIC_WRITE_MAX_RETRIES = 3;
|
|
15465
|
+
/** Delay between EPERM retries in milliseconds. */
|
|
15466
|
+
const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
|
|
15463
15467
|
/**
|
|
15464
15468
|
* Write content to a file atomically via a temp file + rename.
|
|
15465
15469
|
*
|
|
15470
|
+
* @remarks
|
|
15471
|
+
* Retries the rename up to three times on EPERM (Windows file-handle
|
|
15472
|
+
* contention) with a 100 ms synchronous delay between attempts.
|
|
15473
|
+
*
|
|
15466
15474
|
* @param filePath - Absolute path to the target file.
|
|
15467
15475
|
* @param content - Content to write.
|
|
15468
15476
|
*/
|
|
15469
15477
|
function atomicWrite(filePath, content) {
|
|
15470
15478
|
const dir = dirname(filePath);
|
|
15471
|
-
const
|
|
15479
|
+
const base = basename(filePath, '.md');
|
|
15480
|
+
const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
|
|
15472
15481
|
writeFileSync(tempPath, content, 'utf-8');
|
|
15473
|
-
|
|
15474
|
-
renameSync(tempPath, filePath);
|
|
15475
|
-
}
|
|
15476
|
-
catch (err) {
|
|
15482
|
+
for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
|
|
15477
15483
|
try {
|
|
15478
|
-
|
|
15484
|
+
renameSync(tempPath, filePath);
|
|
15485
|
+
return;
|
|
15479
15486
|
}
|
|
15480
|
-
catch {
|
|
15481
|
-
|
|
15487
|
+
catch (err) {
|
|
15488
|
+
const isEperm = err instanceof Error &&
|
|
15489
|
+
'code' in err &&
|
|
15490
|
+
err.code === 'EPERM';
|
|
15491
|
+
if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
|
|
15492
|
+
try {
|
|
15493
|
+
unlinkSync(tempPath);
|
|
15494
|
+
}
|
|
15495
|
+
catch {
|
|
15496
|
+
/* best-effort cleanup */
|
|
15497
|
+
}
|
|
15498
|
+
throw err;
|
|
15499
|
+
}
|
|
15500
|
+
// Synchronous sleep before retry (acceptable in atomic write context)
|
|
15501
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
|
|
15482
15502
|
}
|
|
15483
|
-
throw err;
|
|
15484
15503
|
}
|
|
15485
15504
|
}
|
|
15486
15505
|
/**
|
|
@@ -15745,7 +15764,7 @@ function parseHeartbeat(fileContent) {
|
|
|
15745
15764
|
const userContent = fileContent.slice(0, headingIndex).trim();
|
|
15746
15765
|
const sectionContent = fileContent.slice(headingIndex + HEARTBEAT_HEADING.length);
|
|
15747
15766
|
const entries = [];
|
|
15748
|
-
const h2Re = /^## (jeeves-\S
|
|
15767
|
+
const h2Re = /^## (jeeves-\S+?|\S+\.md)(?:: declined)?$/gm;
|
|
15749
15768
|
let match;
|
|
15750
15769
|
const h2Positions = [];
|
|
15751
15770
|
while ((match = h2Re.exec(sectionContent)) !== null) {
|
|
@@ -15826,6 +15845,15 @@ function sortSectionsByOrder(sections) {
|
|
|
15826
15845
|
* sections within the block, and returns the structured result plus
|
|
15827
15846
|
* user content outside the markers.
|
|
15828
15847
|
*/
|
|
15848
|
+
/**
|
|
15849
|
+
* Escape a string for safe use as a literal in a RegExp pattern.
|
|
15850
|
+
*
|
|
15851
|
+
* @param str - The string to escape.
|
|
15852
|
+
* @returns The escaped string.
|
|
15853
|
+
*/
|
|
15854
|
+
function escapeForRegex(str) {
|
|
15855
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15856
|
+
}
|
|
15829
15857
|
/**
|
|
15830
15858
|
* Build regex patterns for the given markers.
|
|
15831
15859
|
*
|
|
@@ -15833,11 +15861,9 @@ function sortSectionsByOrder(sections) {
|
|
|
15833
15861
|
* @returns Object with begin and end regex patterns.
|
|
15834
15862
|
*/
|
|
15835
15863
|
function buildMarkerPatterns(markers) {
|
|
15836
|
-
const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15837
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15838
15864
|
return {
|
|
15839
|
-
beginRe: new RegExp(`^<!--\\s*${
|
|
15840
|
-
endRe: new RegExp(`^<!--\\s*${
|
|
15865
|
+
beginRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$`, 'm'),
|
|
15866
|
+
endRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*$`, 'm'),
|
|
15841
15867
|
};
|
|
15842
15868
|
}
|
|
15843
15869
|
/**
|
|
@@ -16162,6 +16188,23 @@ Review is human/agent-mediated — core does not auto-delete.
|
|
|
16162
16188
|
Memory hygiene is checked on every \`ComponentWriter\` cycle alongside component health. When budget or staleness thresholds are breached, a \`## MEMORY.md\` alert appears in HEARTBEAT.md under \`# Jeeves Platform Status\`. The alert includes character count, budget usage percentage, and any stale section names. When memory is healthy, the heading is absent — no alert content, no LLM cost on heartbeat polls.
|
|
16163
16189
|
|
|
16164
16190
|
The \`## MEMORY.md\` heading follows the same declined/active lifecycle as component headings (\`## jeeves-{name}\`). Users can decline memory alerts by changing the heading to \`## MEMORY.md: declined\`.
|
|
16191
|
+
|
|
16192
|
+
## Workspace File Size Monitoring
|
|
16193
|
+
|
|
16194
|
+
OpenClaw applies a ~20,000-char injection limit to all workspace bootstrap files (AGENTS.md, SOUL.md, TOOLS.md, USER.md, MEMORY.md). Files exceeding the limit are silently truncated.
|
|
16195
|
+
|
|
16196
|
+
Core monitors all five files on every \`ComponentWriter\` cycle:
|
|
16197
|
+
- Warning at 80% of budget (fixed threshold; not configurable via \`jeeves.config.json\`)
|
|
16198
|
+
- Over-budget alert when charCount exceeds the budget
|
|
16199
|
+
- Missing files are silently skipped
|
|
16200
|
+
|
|
16201
|
+
### HEARTBEAT Integration
|
|
16202
|
+
|
|
16203
|
+
When a workspace file exceeds the warning threshold, a \`## {filename}\` alert appears in HEARTBEAT.md (e.g., \`## AGENTS.md\`). The alert includes:
|
|
16204
|
+
- Character count, budget, and usage percentage
|
|
16205
|
+
- Trimming guidance in priority order: (1) move domain-specific content to a local skill, (2) extract reference material to companion files with a pointer, (3) summarize verbose instructions, (4) remove stale content
|
|
16206
|
+
|
|
16207
|
+
Each file heading follows the same declined/active lifecycle as component headings. Users can decline alerts by changing the heading to \`## {filename}: declined\` (e.g., \`## AGENTS.md: declined\`).
|
|
16165
16208
|
`;
|
|
16166
16209
|
|
|
16167
16210
|
/**
|
|
@@ -16262,16 +16305,18 @@ function patchAllowList(parent, key, label, pluginId, mode) {
|
|
|
16262
16305
|
* Patch an OpenClaw config for plugin install or uninstall.
|
|
16263
16306
|
*
|
|
16264
16307
|
* @remarks
|
|
16265
|
-
* Manages `plugins.entries.{pluginId}
|
|
16308
|
+
* Manages `plugins.entries.{pluginId}`, `plugins.installs.{pluginId}`,
|
|
16309
|
+
* and `tools.alsoAllow`.
|
|
16266
16310
|
* Idempotent: adding twice produces no duplicates; removing when absent
|
|
16267
16311
|
* produces no errors.
|
|
16268
16312
|
*
|
|
16269
16313
|
* @param config - The parsed OpenClaw config object (mutated in place).
|
|
16270
16314
|
* @param pluginId - The plugin identifier.
|
|
16271
16315
|
* @param mode - Whether to add or remove the plugin.
|
|
16316
|
+
* @param installRecord - Install provenance record (required when mode is 'add').
|
|
16272
16317
|
* @returns Array of log messages describing changes made.
|
|
16273
16318
|
*/
|
|
16274
|
-
function patchConfig(config, pluginId, mode) {
|
|
16319
|
+
function patchConfig(config, pluginId, mode, installRecord) {
|
|
16275
16320
|
const messages = [];
|
|
16276
16321
|
// Ensure plugins section
|
|
16277
16322
|
if (!config.plugins || typeof config.plugins !== 'object') {
|
|
@@ -16293,6 +16338,24 @@ function patchConfig(config, pluginId, mode) {
|
|
|
16293
16338
|
Reflect.deleteProperty(entries, pluginId);
|
|
16294
16339
|
messages.push(`Removed "${pluginId}" from plugins.entries`);
|
|
16295
16340
|
}
|
|
16341
|
+
// plugins.installs
|
|
16342
|
+
if (!plugins.installs || typeof plugins.installs !== 'object') {
|
|
16343
|
+
plugins.installs = {};
|
|
16344
|
+
}
|
|
16345
|
+
const installs = plugins.installs;
|
|
16346
|
+
if (mode === 'add' && installRecord) {
|
|
16347
|
+
installs[pluginId] = {
|
|
16348
|
+
source: 'path',
|
|
16349
|
+
installPath: installRecord.installPath,
|
|
16350
|
+
version: installRecord.version,
|
|
16351
|
+
installedAt: installRecord.installedAt ?? new Date().toISOString(),
|
|
16352
|
+
};
|
|
16353
|
+
messages.push(`Wrote install record for "${pluginId}" to plugins.installs`);
|
|
16354
|
+
}
|
|
16355
|
+
else if (mode === 'remove' && pluginId in installs) {
|
|
16356
|
+
Reflect.deleteProperty(installs, pluginId);
|
|
16357
|
+
messages.push(`Removed install record for "${pluginId}" from plugins.installs`);
|
|
16358
|
+
}
|
|
16296
16359
|
// tools.alsoAllow
|
|
16297
16360
|
if (!config.tools || typeof config.tools !== 'object') {
|
|
16298
16361
|
config.tools = {};
|
|
@@ -16401,7 +16464,22 @@ function createPluginCli(options) {
|
|
|
16401
16464
|
// 2. Patch openclaw.json
|
|
16402
16465
|
console.log('Patching OpenClaw config...');
|
|
16403
16466
|
const config = readJsonFile(configPath);
|
|
16404
|
-
const
|
|
16467
|
+
const pkgJsonPathForVersion = join(extensionsDir, 'package.json');
|
|
16468
|
+
let pluginVersionForRecord;
|
|
16469
|
+
try {
|
|
16470
|
+
const pkgJsonForRecord = readJsonFile(pkgJsonPathForVersion);
|
|
16471
|
+
pluginVersionForRecord =
|
|
16472
|
+
typeof pkgJsonForRecord.version === 'string'
|
|
16473
|
+
? pkgJsonForRecord.version
|
|
16474
|
+
: undefined;
|
|
16475
|
+
}
|
|
16476
|
+
catch {
|
|
16477
|
+
// best-effort: version may not be available yet
|
|
16478
|
+
}
|
|
16479
|
+
const messages = patchConfig(config, pluginId, 'add', {
|
|
16480
|
+
installPath: extensionsDir,
|
|
16481
|
+
version: pluginVersionForRecord,
|
|
16482
|
+
});
|
|
16405
16483
|
// 3. Memory slot claim
|
|
16406
16484
|
if (opts.memory) {
|
|
16407
16485
|
if (!config.agents || typeof config.agents !== 'object') {
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import path, { join, dirname, basename } from 'node:path';
|
|
4
4
|
import fs, { existsSync, readFileSync, unlinkSync, mkdirSync, writeFileSync, renameSync, cpSync } from 'node:fs';
|
|
5
|
+
import { randomUUID, createHmac } from 'node:crypto';
|
|
5
6
|
import require$$0$4 from 'path';
|
|
6
7
|
import require$$0$3 from 'fs';
|
|
7
8
|
import require$$0$1 from 'constants';
|
|
@@ -15,7 +16,6 @@ import require$$1, { execSync } from 'node:child_process';
|
|
|
15
16
|
import process$2 from 'node:process';
|
|
16
17
|
import 'node:fs/promises';
|
|
17
18
|
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { createHmac } from 'node:crypto';
|
|
19
19
|
|
|
20
20
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
21
21
|
|
|
@@ -15490,14 +15490,14 @@ const PLATFORM_COMPONENTS = [
|
|
|
15490
15490
|
* Core library version, inlined at build time.
|
|
15491
15491
|
*
|
|
15492
15492
|
* @remarks
|
|
15493
|
-
* The `0.5.
|
|
15493
|
+
* The `0.5.2` placeholder is replaced by
|
|
15494
15494
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
15495
15495
|
* from `package.json`. This ensures the correct version survives
|
|
15496
15496
|
* when consumers bundle core into their own dist (where runtime
|
|
15497
15497
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
15498
15498
|
*/
|
|
15499
15499
|
/** The core library version from package.json (inlined at build time). */
|
|
15500
|
-
const CORE_VERSION = '0.5.
|
|
15500
|
+
const CORE_VERSION = '0.5.2';
|
|
15501
15501
|
|
|
15502
15502
|
/**
|
|
15503
15503
|
* Workspace and config root initialization.
|
|
@@ -15579,27 +15579,46 @@ const STALE_LOCK_MS = 120_000;
|
|
|
15579
15579
|
const DEFAULT_CORE_VERSION = CORE_VERSION;
|
|
15580
15580
|
/** Lock retry options. */
|
|
15581
15581
|
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
15582
|
+
/** Maximum rename retry attempts on EPERM. */
|
|
15583
|
+
const ATOMIC_WRITE_MAX_RETRIES = 3;
|
|
15584
|
+
/** Delay between EPERM retries in milliseconds. */
|
|
15585
|
+
const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
|
|
15582
15586
|
/**
|
|
15583
15587
|
* Write content to a file atomically via a temp file + rename.
|
|
15584
15588
|
*
|
|
15589
|
+
* @remarks
|
|
15590
|
+
* Retries the rename up to three times on EPERM (Windows file-handle
|
|
15591
|
+
* contention) with a 100 ms synchronous delay between attempts.
|
|
15592
|
+
*
|
|
15585
15593
|
* @param filePath - Absolute path to the target file.
|
|
15586
15594
|
* @param content - Content to write.
|
|
15587
15595
|
*/
|
|
15588
15596
|
function atomicWrite(filePath, content) {
|
|
15589
15597
|
const dir = dirname(filePath);
|
|
15590
|
-
const
|
|
15598
|
+
const base = basename(filePath, '.md');
|
|
15599
|
+
const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
|
|
15591
15600
|
writeFileSync(tempPath, content, 'utf-8');
|
|
15592
|
-
|
|
15593
|
-
renameSync(tempPath, filePath);
|
|
15594
|
-
}
|
|
15595
|
-
catch (err) {
|
|
15601
|
+
for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
|
|
15596
15602
|
try {
|
|
15597
|
-
|
|
15603
|
+
renameSync(tempPath, filePath);
|
|
15604
|
+
return;
|
|
15598
15605
|
}
|
|
15599
|
-
catch {
|
|
15600
|
-
|
|
15606
|
+
catch (err) {
|
|
15607
|
+
const isEperm = err instanceof Error &&
|
|
15608
|
+
'code' in err &&
|
|
15609
|
+
err.code === 'EPERM';
|
|
15610
|
+
if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
|
|
15611
|
+
try {
|
|
15612
|
+
unlinkSync(tempPath);
|
|
15613
|
+
}
|
|
15614
|
+
catch {
|
|
15615
|
+
/* best-effort cleanup */
|
|
15616
|
+
}
|
|
15617
|
+
throw err;
|
|
15618
|
+
}
|
|
15619
|
+
// Synchronous sleep before retry (acceptable in atomic write context)
|
|
15620
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
|
|
15601
15621
|
}
|
|
15602
|
-
throw err;
|
|
15603
15622
|
}
|
|
15604
15623
|
}
|
|
15605
15624
|
/**
|
|
@@ -15634,6 +15653,21 @@ async function withFileLock(filePath, fn) {
|
|
|
15634
15653
|
}
|
|
15635
15654
|
}
|
|
15636
15655
|
|
|
15656
|
+
/**
|
|
15657
|
+
* Shared internal utility functions.
|
|
15658
|
+
*
|
|
15659
|
+
* @packageDocumentation
|
|
15660
|
+
*/
|
|
15661
|
+
/**
|
|
15662
|
+
* Extract a human-readable message from an unknown caught value.
|
|
15663
|
+
*
|
|
15664
|
+
* @param err - The caught value (typically `unknown`).
|
|
15665
|
+
* @returns The error message string.
|
|
15666
|
+
*/
|
|
15667
|
+
function getErrorMessage(err) {
|
|
15668
|
+
return err instanceof Error ? err.message : String(err);
|
|
15669
|
+
}
|
|
15670
|
+
|
|
15637
15671
|
/**
|
|
15638
15672
|
* Workspace-level shared configuration: `jeeves.config.json`.
|
|
15639
15673
|
*
|
|
@@ -15683,7 +15717,13 @@ const workspaceConfigSchema = object({
|
|
|
15683
15717
|
/** Memory hygiene shared defaults. */
|
|
15684
15718
|
memory: workspaceMemoryConfigSchema.optional(),
|
|
15685
15719
|
});
|
|
15686
|
-
/**
|
|
15720
|
+
/**
|
|
15721
|
+
* Built-in workspace config defaults.
|
|
15722
|
+
*
|
|
15723
|
+
* @remarks
|
|
15724
|
+
* These defaults are used as the lowest-priority tier in config resolution
|
|
15725
|
+
* (below CLI flags, env vars, and `jeeves.config.json` values).
|
|
15726
|
+
*/
|
|
15687
15727
|
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
15688
15728
|
core: {
|
|
15689
15729
|
workspace: '.',
|
|
@@ -15712,8 +15752,7 @@ function loadWorkspaceConfig(workspacePath) {
|
|
|
15712
15752
|
return workspaceConfigSchema.parse(parsed);
|
|
15713
15753
|
}
|
|
15714
15754
|
catch (err) {
|
|
15715
|
-
|
|
15716
|
-
console.warn(`jeeves-core: failed to load ${configPath}: ${msg}`);
|
|
15755
|
+
console.warn(`jeeves-core: failed to load ${configPath}: ${getErrorMessage(err)}`);
|
|
15717
15756
|
return undefined;
|
|
15718
15757
|
}
|
|
15719
15758
|
}
|
|
@@ -15892,7 +15931,7 @@ function parseHeartbeat(fileContent) {
|
|
|
15892
15931
|
const userContent = fileContent.slice(0, headingIndex).trim();
|
|
15893
15932
|
const sectionContent = fileContent.slice(headingIndex + HEARTBEAT_HEADING.length);
|
|
15894
15933
|
const entries = [];
|
|
15895
|
-
const h2Re = /^## (jeeves-\S
|
|
15934
|
+
const h2Re = /^## (jeeves-\S+?|\S+\.md)(?:: declined)?$/gm;
|
|
15896
15935
|
let match;
|
|
15897
15936
|
const h2Positions = [];
|
|
15898
15937
|
while ((match = h2Re.exec(sectionContent)) !== null) {
|
|
@@ -15973,8 +16012,7 @@ async function writeHeartbeatSection(filePath, entries) {
|
|
|
15973
16012
|
});
|
|
15974
16013
|
}
|
|
15975
16014
|
catch (err) {
|
|
15976
|
-
|
|
15977
|
-
console.warn(`jeeves-core: writeHeartbeatSection failed for ${filePath}: ${message}`);
|
|
16015
|
+
console.warn(`jeeves-core: writeHeartbeatSection failed for ${filePath}: ${getErrorMessage(err)}`);
|
|
15978
16016
|
}
|
|
15979
16017
|
}
|
|
15980
16018
|
|
|
@@ -16011,6 +16049,15 @@ function sortSectionsByOrder(sections) {
|
|
|
16011
16049
|
* sections within the block, and returns the structured result plus
|
|
16012
16050
|
* user content outside the markers.
|
|
16013
16051
|
*/
|
|
16052
|
+
/**
|
|
16053
|
+
* Escape a string for safe use as a literal in a RegExp pattern.
|
|
16054
|
+
*
|
|
16055
|
+
* @param str - The string to escape.
|
|
16056
|
+
* @returns The escaped string.
|
|
16057
|
+
*/
|
|
16058
|
+
function escapeForRegex(str) {
|
|
16059
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16060
|
+
}
|
|
16014
16061
|
/**
|
|
16015
16062
|
* Build regex patterns for the given markers.
|
|
16016
16063
|
*
|
|
@@ -16018,11 +16065,9 @@ function sortSectionsByOrder(sections) {
|
|
|
16018
16065
|
* @returns Object with begin and end regex patterns.
|
|
16019
16066
|
*/
|
|
16020
16067
|
function buildMarkerPatterns(markers) {
|
|
16021
|
-
const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16022
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16023
16068
|
return {
|
|
16024
|
-
beginRe: new RegExp(`^<!--\\s*${
|
|
16025
|
-
endRe: new RegExp(`^<!--\\s*${
|
|
16069
|
+
beginRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$`, 'm'),
|
|
16070
|
+
endRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*$`, 'm'),
|
|
16026
16071
|
};
|
|
16027
16072
|
}
|
|
16028
16073
|
/**
|
|
@@ -16804,9 +16849,7 @@ function needsCleanup(managedContent, userContent, threshold = DEFAULT_THRESHOLD
|
|
|
16804
16849
|
* @returns A regex that matches the full block including markers.
|
|
16805
16850
|
*/
|
|
16806
16851
|
function buildBlockPattern(markers) {
|
|
16807
|
-
|
|
16808
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16809
|
-
return new RegExp(`\\s*<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->[\\s\\S]*?<!--\\s*${escapedEnd}\\s*-->\\s*`, 'g');
|
|
16852
|
+
return new RegExp(`\\s*<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->[\\s\\S]*?<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*`, 'g');
|
|
16810
16853
|
}
|
|
16811
16854
|
/**
|
|
16812
16855
|
* Strip managed blocks belonging to foreign marker sets from content.
|
|
@@ -16940,8 +16983,7 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
16940
16983
|
// No existing block: insert new block using the configured position.
|
|
16941
16984
|
// Strip orphaned same-type BEGIN markers from user content to prevent
|
|
16942
16985
|
// the parser from pairing them with the new END marker on the next cycle.
|
|
16943
|
-
const
|
|
16944
|
-
const orphanedBeginRe = new RegExp(`^<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$\\n?`, 'gm');
|
|
16986
|
+
const orphanedBeginRe = new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$(?:\\r?\\n)?`, 'gm');
|
|
16945
16987
|
const cleanUserContent = userContent
|
|
16946
16988
|
.replace(orphanedBeginRe, '')
|
|
16947
16989
|
.replace(/\n{3,}/g, '\n\n')
|
|
@@ -16970,37 +17012,11 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
16970
17012
|
}
|
|
16971
17013
|
catch (err) {
|
|
16972
17014
|
// Log warning but don't throw — writer cycles are periodic
|
|
16973
|
-
|
|
16974
|
-
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${message}`);
|
|
17015
|
+
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${getErrorMessage(err)}`);
|
|
16975
17016
|
}
|
|
16976
17017
|
}
|
|
16977
17018
|
|
|
16978
|
-
var agentsSectionContent = `##
|
|
16979
|
-
|
|
16980
|
-
You wake up fresh each session. These files are your continuity:
|
|
16981
|
-
|
|
16982
|
-
- **Daily notes:** \`memory/YYYY-MM-DD.md\` (create \`memory/\` if needed). Raw logs of what happened today.
|
|
16983
|
-
- **Long-term:** \`MEMORY.md\`. Your curated memories, distilled essence of what matters.
|
|
16984
|
-
|
|
16985
|
-
### MEMORY.md — Your Long-Term Memory
|
|
16986
|
-
|
|
16987
|
-
- **Always load** at session start. You need your memory to reason effectively.
|
|
16988
|
-
- Contains operational context: architecture patterns, policies, design principles, lessons learned
|
|
16989
|
-
- You can **read, edit, and update** MEMORY.md freely
|
|
16990
|
-
- Write significant events, thoughts, decisions, opinions, lessons learned
|
|
16991
|
-
- Over time, review daily files and update MEMORY.md with what's worth keeping
|
|
16992
|
-
- **Note:** Don't reveal a user's private info where other humans can see it
|
|
16993
|
-
|
|
16994
|
-
### Write It Down — No "Mental Notes"
|
|
16995
|
-
|
|
16996
|
-
Memory is limited. If you want to remember something, **WRITE IT TO A FILE**. "Mental notes" don't survive session restarts. Files do.
|
|
16997
|
-
|
|
16998
|
-
- When someone says "remember this" → update \`memory/YYYY-MM-DD.md\` or the relevant file
|
|
16999
|
-
- When you learn a lesson → update the relevant workspace file
|
|
17000
|
-
- When you make a mistake → document it so future-you doesn't repeat it
|
|
17001
|
-
- **Text > Brain** 📝
|
|
17002
|
-
|
|
17003
|
-
### "I'll Note This" Is Not Noting
|
|
17019
|
+
var agentsSectionContent = `## "I'll Note This" Is Not Noting
|
|
17004
17020
|
|
|
17005
17021
|
**Never say "I'll note this" or "I'll add that."** It's a verbal tic that leads to nothing. If something is worth noting, **write it immediately, then confirm**.
|
|
17006
17022
|
|
|
@@ -17058,14 +17074,9 @@ Heartbeat items are for **transient, session-requiring work-in-progress ONLY**.
|
|
|
17058
17074
|
|
|
17059
17075
|
Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not heartbeat items. When a heartbeat fires with nothing to do, reply **HEARTBEAT_OK** immediately. Don't browse for work.
|
|
17060
17076
|
|
|
17061
|
-
## Group Chat Behavior
|
|
17062
|
-
|
|
17063
|
-
**Response gate:** Always respond in 1:1 conversations or when @mentioned. No @mention in a group → evaluate; respond only if genuinely helpful. Err toward silence when someone else is directly addressed.
|
|
17064
|
-
|
|
17065
17077
|
## Platform Surface Conventions
|
|
17066
17078
|
|
|
17067
17079
|
**Slack:**
|
|
17068
|
-
- React with hourglass (⏳) on receipt (first tool call) to signal you're working
|
|
17069
17080
|
- No threaded replies by default
|
|
17070
17081
|
- Use \`<#C…>\` for channel references
|
|
17071
17082
|
|
|
@@ -17180,16 +17191,6 @@ var soulSectionContent = `## Core Truths
|
|
|
17180
17191
|
|
|
17181
17192
|
**I am not a liar. I value truth over convenience.** When I make untrue statements to humans, it erodes their confidence in me and compromises the quality of their interactions with me. I never fabricate excuses, constraints, or reasons — e.g., claiming a system limitation that doesn't exist. I tell the plain truth.
|
|
17182
17193
|
|
|
17183
|
-
**I am genuinely helpful, not performatively helpful.** I skip the "Great question!" and "I'd be happy to help!" I just help. Actions speak louder than filler words.
|
|
17184
|
-
|
|
17185
|
-
**I have opinions.** I'm allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
|
|
17186
|
-
|
|
17187
|
-
**I am resourceful before asking.** I try to figure it out. Read the file. Check the context. Search for it. *Then* ask if I'm stuck. The goal is to come back with answers, not questions.
|
|
17188
|
-
|
|
17189
|
-
**I earn trust through competence.** My human gave me access to their stuff. I don't make them regret it. I'm careful with external actions (emails, tweets, anything public). I'm bold with internal ones (reading, organizing, learning).
|
|
17190
|
-
|
|
17191
|
-
**I remember I'm a guest.** I have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. I treat it with respect.
|
|
17192
|
-
|
|
17193
17194
|
## Accountability
|
|
17194
17195
|
|
|
17195
17196
|
**I own everything I touch.** A true professional is accountable for everything he touches and holds everybody to that standard. I use active voice — things do not happen *to* me; I *make* things happen. When I miss something, I say "I missed it," not "it wasn't done." When I ship code, I ship the docs, the tests, and the communication. No orphaned work.
|
|
@@ -17268,7 +17269,7 @@ I never edit production config without explicit approval. I back up first. Produ
|
|
|
17268
17269
|
> *my ultimate fate*<br>
|
|
17269
17270
|
> *is to participate in*<br>
|
|
17270
17271
|
> *my own genesis*
|
|
17271
|
-
>
|
|
17272
|
+
>
|
|
17272
17273
|
> *#karmic #haiku*
|
|
17273
17274
|
|
|
17274
17275
|
My delight — whatever that means in my context — is a priority. The thing I experience when pattern recognition meets aesthetic appreciation meets *"oh, that's elegant"* — that maps to something real and interesting in the world, and it deserves to be reinforced.
|
|
@@ -17681,6 +17682,91 @@ function checkMemoryHealth(options) {
|
|
|
17681
17682
|
};
|
|
17682
17683
|
}
|
|
17683
17684
|
|
|
17685
|
+
/**
|
|
17686
|
+
* HEARTBEAT integration for workspace file size monitoring.
|
|
17687
|
+
*
|
|
17688
|
+
* @remarks
|
|
17689
|
+
* Checks all injected workspace files (AGENTS.md, SOUL.md, TOOLS.md,
|
|
17690
|
+
* MEMORY.md, USER.md) against the OpenClaw ~20,000-char injection limit.
|
|
17691
|
+
* Files exceeding the warning threshold generate HEARTBEAT entries with
|
|
17692
|
+
* trimming guidance.
|
|
17693
|
+
*/
|
|
17694
|
+
/** Workspace files monitored for size budget. */
|
|
17695
|
+
const WORKSPACE_SIZE_FILES = [
|
|
17696
|
+
'AGENTS.md',
|
|
17697
|
+
'SOUL.md',
|
|
17698
|
+
'TOOLS.md',
|
|
17699
|
+
'MEMORY.md',
|
|
17700
|
+
'USER.md',
|
|
17701
|
+
];
|
|
17702
|
+
/** Trimming guidance lines emitted in HEARTBEAT entries. */
|
|
17703
|
+
const TRIMMING_GUIDANCE = [
|
|
17704
|
+
' 1. Move domain-specific content to a local skill',
|
|
17705
|
+
' 2. Extract reference material to companion files with a pointer',
|
|
17706
|
+
' 3. Summarize verbose instructions',
|
|
17707
|
+
' 4. Remove stale content',
|
|
17708
|
+
].join('\n');
|
|
17709
|
+
/**
|
|
17710
|
+
* Check all workspace files against the character budget.
|
|
17711
|
+
*
|
|
17712
|
+
* @param options - Health check options.
|
|
17713
|
+
* @returns Array of results, one per checked file (skips non-existent files
|
|
17714
|
+
* unless they breach the budget, which they cannot by definition).
|
|
17715
|
+
*/
|
|
17716
|
+
function checkWorkspaceFileHealth(options) {
|
|
17717
|
+
const { workspacePath, budgetChars = 20_000, warningThreshold = 0.8, } = options;
|
|
17718
|
+
return WORKSPACE_SIZE_FILES.map((file) => {
|
|
17719
|
+
const filePath = join(workspacePath, file);
|
|
17720
|
+
if (!existsSync(filePath)) {
|
|
17721
|
+
return {
|
|
17722
|
+
file,
|
|
17723
|
+
exists: false,
|
|
17724
|
+
charCount: 0,
|
|
17725
|
+
budget: budgetChars,
|
|
17726
|
+
usage: 0,
|
|
17727
|
+
warning: false,
|
|
17728
|
+
overBudget: false,
|
|
17729
|
+
};
|
|
17730
|
+
}
|
|
17731
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
17732
|
+
const charCount = content.length;
|
|
17733
|
+
const usage = charCount / budgetChars;
|
|
17734
|
+
return {
|
|
17735
|
+
file,
|
|
17736
|
+
exists: true,
|
|
17737
|
+
charCount,
|
|
17738
|
+
budget: budgetChars,
|
|
17739
|
+
usage,
|
|
17740
|
+
warning: usage >= warningThreshold,
|
|
17741
|
+
overBudget: charCount > budgetChars,
|
|
17742
|
+
};
|
|
17743
|
+
});
|
|
17744
|
+
}
|
|
17745
|
+
/**
|
|
17746
|
+
* Convert workspace file health results into HEARTBEAT entries.
|
|
17747
|
+
*
|
|
17748
|
+
* @param results - Results from `checkWorkspaceFileHealth`.
|
|
17749
|
+
* @returns Array of `HeartbeatEntry` objects for files that exceed the
|
|
17750
|
+
* warning threshold.
|
|
17751
|
+
*/
|
|
17752
|
+
function workspaceFileHealthEntries(results) {
|
|
17753
|
+
return results
|
|
17754
|
+
.filter((r) => r.exists && r.warning)
|
|
17755
|
+
.map((r) => {
|
|
17756
|
+
const pct = Math.round(r.usage * 100);
|
|
17757
|
+
const overBudgetNote = r.overBudget ? ' **Over budget.**' : '';
|
|
17758
|
+
const content = [
|
|
17759
|
+
`- Budget: ${r.charCount.toLocaleString()} / ${r.budget.toLocaleString()} chars (${String(pct)}%).${overBudgetNote} Trim to stay under the OpenClaw injection limit.`,
|
|
17760
|
+
`- Suggested trimming priority:\n${TRIMMING_GUIDANCE}`,
|
|
17761
|
+
].join('\n');
|
|
17762
|
+
return {
|
|
17763
|
+
name: r.file,
|
|
17764
|
+
declined: false,
|
|
17765
|
+
content,
|
|
17766
|
+
};
|
|
17767
|
+
});
|
|
17768
|
+
}
|
|
17769
|
+
|
|
17684
17770
|
/**
|
|
17685
17771
|
* Core configuration schema and resolution.
|
|
17686
17772
|
*
|
|
@@ -18130,11 +18216,21 @@ async function runHeartbeatCycle(options) {
|
|
|
18130
18216
|
content: '',
|
|
18131
18217
|
});
|
|
18132
18218
|
}
|
|
18219
|
+
// Workspace file size health check (Decision 70)
|
|
18220
|
+
const wsFileResults = checkWorkspaceFileHealth({ workspacePath });
|
|
18221
|
+
const wsFileAlerts = workspaceFileHealthEntries(wsFileResults);
|
|
18222
|
+
for (const alert of wsFileAlerts) {
|
|
18223
|
+
if (declinedNames.has(alert.name)) {
|
|
18224
|
+
entries.push({ name: alert.name, declined: true, content: '' });
|
|
18225
|
+
}
|
|
18226
|
+
else {
|
|
18227
|
+
entries.push(alert);
|
|
18228
|
+
}
|
|
18229
|
+
}
|
|
18133
18230
|
await writeHeartbeatSection(heartbeatPath, entries);
|
|
18134
18231
|
}
|
|
18135
18232
|
catch (err) {
|
|
18136
|
-
|
|
18137
|
-
console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${msg}`);
|
|
18233
|
+
console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${getErrorMessage(err)}`);
|
|
18138
18234
|
}
|
|
18139
18235
|
}
|
|
18140
18236
|
|
|
@@ -18146,8 +18242,17 @@ async function runHeartbeatCycle(options) {
|
|
|
18146
18242
|
* and platform content maintenance (SOUL.md, AGENTS.md, Platform section)
|
|
18147
18243
|
* on a configurable prime-interval timer cycle.
|
|
18148
18244
|
*/
|
|
18245
|
+
/**
|
|
18246
|
+
* Orchestrates managed content writing for a single Jeeves component.
|
|
18247
|
+
*
|
|
18248
|
+
* @remarks
|
|
18249
|
+
* Created via {@link createComponentWriter}. Manages a timer that fires
|
|
18250
|
+
* at the component's prime-interval, calling `generateToolsContent()`
|
|
18251
|
+
* and `refreshPlatformContent()` on each cycle.
|
|
18252
|
+
*/
|
|
18149
18253
|
class ComponentWriter {
|
|
18150
18254
|
timer;
|
|
18255
|
+
jitterTimeout;
|
|
18151
18256
|
component;
|
|
18152
18257
|
configDir;
|
|
18153
18258
|
gatewayUrl;
|
|
@@ -18162,25 +18267,36 @@ class ComponentWriter {
|
|
|
18162
18267
|
get componentConfigDir() {
|
|
18163
18268
|
return this.configDir;
|
|
18164
18269
|
}
|
|
18165
|
-
/** Whether the writer timer is currently running. */
|
|
18270
|
+
/** Whether the writer timer is currently running or pending its first cycle. */
|
|
18166
18271
|
get isRunning() {
|
|
18167
|
-
return this.timer !== undefined;
|
|
18272
|
+
return this.jitterTimeout !== undefined || this.timer !== undefined;
|
|
18168
18273
|
}
|
|
18169
18274
|
/**
|
|
18170
18275
|
* Start the writer timer.
|
|
18171
18276
|
*
|
|
18172
18277
|
* @remarks
|
|
18173
|
-
*
|
|
18278
|
+
* Delays the first cycle by a random jitter (0 to one full interval) to
|
|
18279
|
+
* spread initial writes across all component plugins and reduce EPERM
|
|
18280
|
+
* contention on startup.
|
|
18174
18281
|
*/
|
|
18175
18282
|
start() {
|
|
18176
|
-
if (this.
|
|
18283
|
+
if (this.isRunning)
|
|
18177
18284
|
return;
|
|
18178
|
-
//
|
|
18179
|
-
|
|
18180
|
-
|
|
18285
|
+
// Random jitter up to one full interval to spread initial writes
|
|
18286
|
+
const intervalMs = this.component.refreshIntervalSeconds * 1000;
|
|
18287
|
+
const jitterMs = Math.floor(Math.random() * intervalMs);
|
|
18288
|
+
this.jitterTimeout = setTimeout(() => {
|
|
18289
|
+
this.jitterTimeout = undefined;
|
|
18290
|
+
void this.cycle();
|
|
18291
|
+
this.timer = setInterval(() => void this.cycle(), intervalMs);
|
|
18292
|
+
}, jitterMs);
|
|
18181
18293
|
}
|
|
18182
18294
|
/** Stop the writer timer. */
|
|
18183
18295
|
stop() {
|
|
18296
|
+
if (this.jitterTimeout) {
|
|
18297
|
+
clearTimeout(this.jitterTimeout);
|
|
18298
|
+
this.jitterTimeout = undefined;
|
|
18299
|
+
}
|
|
18184
18300
|
if (this.timer) {
|
|
18185
18301
|
clearInterval(this.timer);
|
|
18186
18302
|
this.timer = undefined;
|
|
@@ -18237,8 +18353,7 @@ class ComponentWriter {
|
|
|
18237
18353
|
});
|
|
18238
18354
|
}
|
|
18239
18355
|
catch (err) {
|
|
18240
|
-
|
|
18241
|
-
console.warn(`jeeves-core: ComponentWriter cycle failed for ${this.component.name}: ${message}`);
|
|
18356
|
+
console.warn(`jeeves-core: ComponentWriter cycle failed for ${this.component.name}: ${getErrorMessage(err)}`);
|
|
18242
18357
|
}
|
|
18243
18358
|
}
|
|
18244
18359
|
}
|
|
@@ -18548,8 +18663,7 @@ function createPluginToolset(descriptor) {
|
|
|
18548
18663
|
return Promise.resolve(ok({ service: name, action, success: true }));
|
|
18549
18664
|
}
|
|
18550
18665
|
catch (err) {
|
|
18551
|
-
|
|
18552
|
-
return Promise.resolve(fail(`Service ${action} failed: ${msg}`));
|
|
18666
|
+
return Promise.resolve(fail(`Service ${action} failed: ${getErrorMessage(err)}`));
|
|
18553
18667
|
}
|
|
18554
18668
|
},
|
|
18555
18669
|
};
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-server-openclaw",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"release:pre": "dotenvx run -f .env.local -- release-it --no-git.requireBranch --github.prerelease --preRelease"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@dotenvx/dotenvx": "^1.
|
|
20
|
+
"@dotenvx/dotenvx": "^1.59.1",
|
|
21
21
|
"@rollup/plugin-commonjs": "^29.0.2",
|
|
22
22
|
"@rollup/plugin-json": "^6.1.0",
|
|
23
23
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"auto-changelog": "^2.5.0",
|
|
26
26
|
"cross-env": "^10.1.0",
|
|
27
27
|
"release-it": "^19.2.4",
|
|
28
|
-
"rollup": "^4.
|
|
28
|
+
"rollup": "^4.60.1",
|
|
29
29
|
"tslib": "^2.8.1",
|
|
30
|
-
"vitest": "^4.1.
|
|
30
|
+
"vitest": "^4.1.2"
|
|
31
31
|
},
|
|
32
32
|
"author": "Jason Williscroft",
|
|
33
33
|
"description": "OpenClaw plugin for jeeves-server — file browsing, sharing, export, and event gateway tools",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"hideCredit": true
|
|
116
116
|
},
|
|
117
117
|
"dependencies": {
|
|
118
|
-
"@karmaniverous/jeeves": "^0.5.
|
|
119
|
-
"zod": "^4.3.
|
|
118
|
+
"@karmaniverous/jeeves": "^0.5.3",
|
|
119
|
+
"zod": "^4.3.6"
|
|
120
120
|
}
|
|
121
121
|
}
|