@karmaniverous/jeeves-watcher-openclaw 0.14.0 → 0.14.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/content/agents-section.md +1 -31
- package/content/skill.md +17 -0
- package/content/soul-section.md +1 -11
- package/dist/cli.js +99 -21
- package/dist/index.js +198 -84
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
|
@@ -1,29 +1,4 @@
|
|
|
1
|
-
##
|
|
2
|
-
|
|
3
|
-
You wake up fresh each session. These files are your continuity:
|
|
4
|
-
|
|
5
|
-
- **Daily notes:** `memory/YYYY-MM-DD.md` (create `memory/` if needed). Raw logs of what happened today.
|
|
6
|
-
- **Long-term:** `MEMORY.md`. Your curated memories, distilled essence of what matters.
|
|
7
|
-
|
|
8
|
-
### MEMORY.md — Your Long-Term Memory
|
|
9
|
-
|
|
10
|
-
- **Always load** at session start. You need your memory to reason effectively.
|
|
11
|
-
- Contains operational context: architecture patterns, policies, design principles, lessons learned
|
|
12
|
-
- You can **read, edit, and update** MEMORY.md freely
|
|
13
|
-
- Write significant events, thoughts, decisions, opinions, lessons learned
|
|
14
|
-
- Over time, review daily files and update MEMORY.md with what's worth keeping
|
|
15
|
-
- **Note:** Don't reveal a user's private info where other humans can see it
|
|
16
|
-
|
|
17
|
-
### Write It Down — No "Mental Notes"
|
|
18
|
-
|
|
19
|
-
Memory is limited. If you want to remember something, **WRITE IT TO A FILE**. "Mental notes" don't survive session restarts. Files do.
|
|
20
|
-
|
|
21
|
-
- When someone says "remember this" → update `memory/YYYY-MM-DD.md` or the relevant file
|
|
22
|
-
- When you learn a lesson → update the relevant workspace file
|
|
23
|
-
- When you make a mistake → document it so future-you doesn't repeat it
|
|
24
|
-
- **Text > Brain** 📝
|
|
25
|
-
|
|
26
|
-
### "I'll Note This" Is Not Noting
|
|
1
|
+
## "I'll Note This" Is Not Noting
|
|
27
2
|
|
|
28
3
|
**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**.
|
|
29
4
|
|
|
@@ -81,14 +56,9 @@ Heartbeat items are for **transient, session-requiring work-in-progress ONLY**.
|
|
|
81
56
|
|
|
82
57
|
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.
|
|
83
58
|
|
|
84
|
-
## Group Chat Behavior
|
|
85
|
-
|
|
86
|
-
**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.
|
|
87
|
-
|
|
88
59
|
## Platform Surface Conventions
|
|
89
60
|
|
|
90
61
|
**Slack:**
|
|
91
|
-
- React with hourglass (⏳) on receipt (first tool call) to signal you're working
|
|
92
62
|
- No threaded replies by default
|
|
93
63
|
- Use `<#C…>` for channel references
|
|
94
64
|
|
package/content/skill.md
CHANGED
|
@@ -103,3 +103,20 @@ Review is human/agent-mediated — core does not auto-delete.
|
|
|
103
103
|
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.
|
|
104
104
|
|
|
105
105
|
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`.
|
|
106
|
+
|
|
107
|
+
## Workspace File Size Monitoring
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
Core monitors all five files on every `ComponentWriter` cycle:
|
|
112
|
+
- Warning at 80% of budget (fixed threshold; not configurable via `jeeves.config.json`)
|
|
113
|
+
- Over-budget alert when charCount exceeds the budget
|
|
114
|
+
- Missing files are silently skipped
|
|
115
|
+
|
|
116
|
+
### HEARTBEAT Integration
|
|
117
|
+
|
|
118
|
+
When a workspace file exceeds the warning threshold, a `## {filename}` alert appears in HEARTBEAT.md (e.g., `## AGENTS.md`). The alert includes:
|
|
119
|
+
- Character count, budget, and usage percentage
|
|
120
|
+
- 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
|
|
121
|
+
|
|
122
|
+
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`).
|
package/content/soul-section.md
CHANGED
|
@@ -2,16 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
**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.
|
|
4
4
|
|
|
5
|
-
**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.
|
|
6
|
-
|
|
7
|
-
**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.
|
|
8
|
-
|
|
9
|
-
**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.
|
|
10
|
-
|
|
11
|
-
**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).
|
|
12
|
-
|
|
13
|
-
**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.
|
|
14
|
-
|
|
15
5
|
## Accountability
|
|
16
6
|
|
|
17
7
|
**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.
|
|
@@ -90,7 +80,7 @@ I never edit production config without explicit approval. I back up first. Produ
|
|
|
90
80
|
> *my ultimate fate*<br>
|
|
91
81
|
> *is to participate in*<br>
|
|
92
82
|
> *my own genesis*
|
|
93
|
-
>
|
|
83
|
+
>
|
|
94
84
|
> *#karmic #haiku*
|
|
95
85
|
|
|
96
86
|
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.
|
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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs, { readFileSync, existsSync, unlinkSync, mkdirSync, writeFileSync, renameSync, cpSync } from 'node:fs';
|
|
2
2
|
import path, { join, dirname, basename } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import require$$0$4 from 'path';
|
|
4
5
|
import require$$0$3 from 'fs';
|
|
5
6
|
import require$$0$1 from 'constants';
|
|
@@ -14,7 +15,6 @@ import process$2 from 'node:process';
|
|
|
14
15
|
import 'node:fs/promises';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
16
17
|
import { homedir } from 'node:os';
|
|
17
|
-
import 'node:crypto';
|
|
18
18
|
|
|
19
19
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
20
20
|
|
|
@@ -15480,14 +15480,14 @@ const PLATFORM_COMPONENTS = [
|
|
|
15480
15480
|
* Core library version, inlined at build time.
|
|
15481
15481
|
*
|
|
15482
15482
|
* @remarks
|
|
15483
|
-
* The `0.5.
|
|
15483
|
+
* The `0.5.2` placeholder is replaced by
|
|
15484
15484
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
15485
15485
|
* from `package.json`. This ensures the correct version survives
|
|
15486
15486
|
* when consumers bundle core into their own dist (where runtime
|
|
15487
15487
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
15488
15488
|
*/
|
|
15489
15489
|
/** The core library version from package.json (inlined at build time). */
|
|
15490
|
-
const CORE_VERSION = '0.5.
|
|
15490
|
+
const CORE_VERSION = '0.5.2';
|
|
15491
15491
|
|
|
15492
15492
|
/**
|
|
15493
15493
|
* Workspace and config root initialization.
|
|
@@ -15569,27 +15569,46 @@ const STALE_LOCK_MS = 120_000;
|
|
|
15569
15569
|
const DEFAULT_CORE_VERSION = CORE_VERSION;
|
|
15570
15570
|
/** Lock retry options. */
|
|
15571
15571
|
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
15572
|
+
/** Maximum rename retry attempts on EPERM. */
|
|
15573
|
+
const ATOMIC_WRITE_MAX_RETRIES = 3;
|
|
15574
|
+
/** Delay between EPERM retries in milliseconds. */
|
|
15575
|
+
const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
|
|
15572
15576
|
/**
|
|
15573
15577
|
* Write content to a file atomically via a temp file + rename.
|
|
15574
15578
|
*
|
|
15579
|
+
* @remarks
|
|
15580
|
+
* Retries the rename up to three times on EPERM (Windows file-handle
|
|
15581
|
+
* contention) with a 100 ms synchronous delay between attempts.
|
|
15582
|
+
*
|
|
15575
15583
|
* @param filePath - Absolute path to the target file.
|
|
15576
15584
|
* @param content - Content to write.
|
|
15577
15585
|
*/
|
|
15578
15586
|
function atomicWrite(filePath, content) {
|
|
15579
15587
|
const dir = dirname(filePath);
|
|
15580
|
-
const
|
|
15588
|
+
const base = basename(filePath, '.md');
|
|
15589
|
+
const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
|
|
15581
15590
|
writeFileSync(tempPath, content, 'utf-8');
|
|
15582
|
-
|
|
15583
|
-
renameSync(tempPath, filePath);
|
|
15584
|
-
}
|
|
15585
|
-
catch (err) {
|
|
15591
|
+
for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
|
|
15586
15592
|
try {
|
|
15587
|
-
|
|
15593
|
+
renameSync(tempPath, filePath);
|
|
15594
|
+
return;
|
|
15588
15595
|
}
|
|
15589
|
-
catch {
|
|
15590
|
-
|
|
15596
|
+
catch (err) {
|
|
15597
|
+
const isEperm = err instanceof Error &&
|
|
15598
|
+
'code' in err &&
|
|
15599
|
+
err.code === 'EPERM';
|
|
15600
|
+
if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
|
|
15601
|
+
try {
|
|
15602
|
+
unlinkSync(tempPath);
|
|
15603
|
+
}
|
|
15604
|
+
catch {
|
|
15605
|
+
/* best-effort cleanup */
|
|
15606
|
+
}
|
|
15607
|
+
throw err;
|
|
15608
|
+
}
|
|
15609
|
+
// Synchronous sleep before retry (acceptable in atomic write context)
|
|
15610
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
|
|
15591
15611
|
}
|
|
15592
|
-
throw err;
|
|
15593
15612
|
}
|
|
15594
15613
|
}
|
|
15595
15614
|
/**
|
|
@@ -15624,6 +15643,21 @@ async function withFileLock(filePath, fn) {
|
|
|
15624
15643
|
}
|
|
15625
15644
|
}
|
|
15626
15645
|
|
|
15646
|
+
/**
|
|
15647
|
+
* Shared internal utility functions.
|
|
15648
|
+
*
|
|
15649
|
+
* @packageDocumentation
|
|
15650
|
+
*/
|
|
15651
|
+
/**
|
|
15652
|
+
* Extract a human-readable message from an unknown caught value.
|
|
15653
|
+
*
|
|
15654
|
+
* @param err - The caught value (typically `unknown`).
|
|
15655
|
+
* @returns The error message string.
|
|
15656
|
+
*/
|
|
15657
|
+
function getErrorMessage(err) {
|
|
15658
|
+
return err instanceof Error ? err.message : String(err);
|
|
15659
|
+
}
|
|
15660
|
+
|
|
15627
15661
|
/**
|
|
15628
15662
|
* Workspace-level shared configuration: `jeeves.config.json`.
|
|
15629
15663
|
*
|
|
@@ -15673,7 +15707,13 @@ const workspaceConfigSchema = object({
|
|
|
15673
15707
|
/** Memory hygiene shared defaults. */
|
|
15674
15708
|
memory: workspaceMemoryConfigSchema.optional(),
|
|
15675
15709
|
});
|
|
15676
|
-
/**
|
|
15710
|
+
/**
|
|
15711
|
+
* Built-in workspace config defaults.
|
|
15712
|
+
*
|
|
15713
|
+
* @remarks
|
|
15714
|
+
* These defaults are used as the lowest-priority tier in config resolution
|
|
15715
|
+
* (below CLI flags, env vars, and `jeeves.config.json` values).
|
|
15716
|
+
*/
|
|
15677
15717
|
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
15678
15718
|
core: {
|
|
15679
15719
|
workspace: '.',
|
|
@@ -15702,8 +15742,7 @@ function loadWorkspaceConfig(workspacePath) {
|
|
|
15702
15742
|
return workspaceConfigSchema.parse(parsed);
|
|
15703
15743
|
}
|
|
15704
15744
|
catch (err) {
|
|
15705
|
-
|
|
15706
|
-
console.warn(`jeeves-core: failed to load ${configPath}: ${msg}`);
|
|
15745
|
+
console.warn(`jeeves-core: failed to load ${configPath}: ${getErrorMessage(err)}`);
|
|
15707
15746
|
return undefined;
|
|
15708
15747
|
}
|
|
15709
15748
|
}
|
|
@@ -15882,7 +15921,7 @@ function parseHeartbeat(fileContent) {
|
|
|
15882
15921
|
const userContent = fileContent.slice(0, headingIndex).trim();
|
|
15883
15922
|
const sectionContent = fileContent.slice(headingIndex + HEARTBEAT_HEADING.length);
|
|
15884
15923
|
const entries = [];
|
|
15885
|
-
const h2Re = /^## (jeeves-\S
|
|
15924
|
+
const h2Re = /^## (jeeves-\S+?|\S+\.md)(?:: declined)?$/gm;
|
|
15886
15925
|
let match;
|
|
15887
15926
|
const h2Positions = [];
|
|
15888
15927
|
while ((match = h2Re.exec(sectionContent)) !== null) {
|
|
@@ -15963,8 +16002,7 @@ async function writeHeartbeatSection(filePath, entries) {
|
|
|
15963
16002
|
});
|
|
15964
16003
|
}
|
|
15965
16004
|
catch (err) {
|
|
15966
|
-
|
|
15967
|
-
console.warn(`jeeves-core: writeHeartbeatSection failed for ${filePath}: ${message}`);
|
|
16005
|
+
console.warn(`jeeves-core: writeHeartbeatSection failed for ${filePath}: ${getErrorMessage(err)}`);
|
|
15968
16006
|
}
|
|
15969
16007
|
}
|
|
15970
16008
|
|
|
@@ -16001,6 +16039,15 @@ function sortSectionsByOrder(sections) {
|
|
|
16001
16039
|
* sections within the block, and returns the structured result plus
|
|
16002
16040
|
* user content outside the markers.
|
|
16003
16041
|
*/
|
|
16042
|
+
/**
|
|
16043
|
+
* Escape a string for safe use as a literal in a RegExp pattern.
|
|
16044
|
+
*
|
|
16045
|
+
* @param str - The string to escape.
|
|
16046
|
+
* @returns The escaped string.
|
|
16047
|
+
*/
|
|
16048
|
+
function escapeForRegex(str) {
|
|
16049
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16050
|
+
}
|
|
16004
16051
|
/**
|
|
16005
16052
|
* Build regex patterns for the given markers.
|
|
16006
16053
|
*
|
|
@@ -16008,11 +16055,9 @@ function sortSectionsByOrder(sections) {
|
|
|
16008
16055
|
* @returns Object with begin and end regex patterns.
|
|
16009
16056
|
*/
|
|
16010
16057
|
function buildMarkerPatterns(markers) {
|
|
16011
|
-
const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16012
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16013
16058
|
return {
|
|
16014
|
-
beginRe: new RegExp(`^<!--\\s*${
|
|
16015
|
-
endRe: new RegExp(`^<!--\\s*${
|
|
16059
|
+
beginRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$`, 'm'),
|
|
16060
|
+
endRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*$`, 'm'),
|
|
16016
16061
|
};
|
|
16017
16062
|
}
|
|
16018
16063
|
/**
|
|
@@ -16794,9 +16839,7 @@ function needsCleanup(managedContent, userContent, threshold = DEFAULT_THRESHOLD
|
|
|
16794
16839
|
* @returns A regex that matches the full block including markers.
|
|
16795
16840
|
*/
|
|
16796
16841
|
function buildBlockPattern(markers) {
|
|
16797
|
-
|
|
16798
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16799
|
-
return new RegExp(`\\s*<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->[\\s\\S]*?<!--\\s*${escapedEnd}\\s*-->\\s*`, 'g');
|
|
16842
|
+
return new RegExp(`\\s*<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->[\\s\\S]*?<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*`, 'g');
|
|
16800
16843
|
}
|
|
16801
16844
|
/**
|
|
16802
16845
|
* Strip managed blocks belonging to foreign marker sets from content.
|
|
@@ -16930,8 +16973,7 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
16930
16973
|
// No existing block: insert new block using the configured position.
|
|
16931
16974
|
// Strip orphaned same-type BEGIN markers from user content to prevent
|
|
16932
16975
|
// the parser from pairing them with the new END marker on the next cycle.
|
|
16933
|
-
const
|
|
16934
|
-
const orphanedBeginRe = new RegExp(`^<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$\\n?`, 'gm');
|
|
16976
|
+
const orphanedBeginRe = new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$(?:\\r?\\n)?`, 'gm');
|
|
16935
16977
|
const cleanUserContent = userContent
|
|
16936
16978
|
.replace(orphanedBeginRe, '')
|
|
16937
16979
|
.replace(/\n{3,}/g, '\n\n')
|
|
@@ -16960,37 +17002,11 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
16960
17002
|
}
|
|
16961
17003
|
catch (err) {
|
|
16962
17004
|
// Log warning but don't throw — writer cycles are periodic
|
|
16963
|
-
|
|
16964
|
-
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${message}`);
|
|
17005
|
+
console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${getErrorMessage(err)}`);
|
|
16965
17006
|
}
|
|
16966
17007
|
}
|
|
16967
17008
|
|
|
16968
|
-
var agentsSectionContent = `##
|
|
16969
|
-
|
|
16970
|
-
You wake up fresh each session. These files are your continuity:
|
|
16971
|
-
|
|
16972
|
-
- **Daily notes:** \`memory/YYYY-MM-DD.md\` (create \`memory/\` if needed). Raw logs of what happened today.
|
|
16973
|
-
- **Long-term:** \`MEMORY.md\`. Your curated memories, distilled essence of what matters.
|
|
16974
|
-
|
|
16975
|
-
### MEMORY.md — Your Long-Term Memory
|
|
16976
|
-
|
|
16977
|
-
- **Always load** at session start. You need your memory to reason effectively.
|
|
16978
|
-
- Contains operational context: architecture patterns, policies, design principles, lessons learned
|
|
16979
|
-
- You can **read, edit, and update** MEMORY.md freely
|
|
16980
|
-
- Write significant events, thoughts, decisions, opinions, lessons learned
|
|
16981
|
-
- Over time, review daily files and update MEMORY.md with what's worth keeping
|
|
16982
|
-
- **Note:** Don't reveal a user's private info where other humans can see it
|
|
16983
|
-
|
|
16984
|
-
### Write It Down — No "Mental Notes"
|
|
16985
|
-
|
|
16986
|
-
Memory is limited. If you want to remember something, **WRITE IT TO A FILE**. "Mental notes" don't survive session restarts. Files do.
|
|
16987
|
-
|
|
16988
|
-
- When someone says "remember this" → update \`memory/YYYY-MM-DD.md\` or the relevant file
|
|
16989
|
-
- When you learn a lesson → update the relevant workspace file
|
|
16990
|
-
- When you make a mistake → document it so future-you doesn't repeat it
|
|
16991
|
-
- **Text > Brain** 📝
|
|
16992
|
-
|
|
16993
|
-
### "I'll Note This" Is Not Noting
|
|
17009
|
+
var agentsSectionContent = `## "I'll Note This" Is Not Noting
|
|
16994
17010
|
|
|
16995
17011
|
**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**.
|
|
16996
17012
|
|
|
@@ -17048,14 +17064,9 @@ Heartbeat items are for **transient, session-requiring work-in-progress ONLY**.
|
|
|
17048
17064
|
|
|
17049
17065
|
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.
|
|
17050
17066
|
|
|
17051
|
-
## Group Chat Behavior
|
|
17052
|
-
|
|
17053
|
-
**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.
|
|
17054
|
-
|
|
17055
17067
|
## Platform Surface Conventions
|
|
17056
17068
|
|
|
17057
17069
|
**Slack:**
|
|
17058
|
-
- React with hourglass (⏳) on receipt (first tool call) to signal you're working
|
|
17059
17070
|
- No threaded replies by default
|
|
17060
17071
|
- Use \`<#C…>\` for channel references
|
|
17061
17072
|
|
|
@@ -17170,16 +17181,6 @@ var soulSectionContent = `## Core Truths
|
|
|
17170
17181
|
|
|
17171
17182
|
**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.
|
|
17172
17183
|
|
|
17173
|
-
**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.
|
|
17174
|
-
|
|
17175
|
-
**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.
|
|
17176
|
-
|
|
17177
|
-
**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.
|
|
17178
|
-
|
|
17179
|
-
**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).
|
|
17180
|
-
|
|
17181
|
-
**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.
|
|
17182
|
-
|
|
17183
17184
|
## Accountability
|
|
17184
17185
|
|
|
17185
17186
|
**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.
|
|
@@ -17258,7 +17259,7 @@ I never edit production config without explicit approval. I back up first. Produ
|
|
|
17258
17259
|
> *my ultimate fate*<br>
|
|
17259
17260
|
> *is to participate in*<br>
|
|
17260
17261
|
> *my own genesis*
|
|
17261
|
-
>
|
|
17262
|
+
>
|
|
17262
17263
|
> *#karmic #haiku*
|
|
17263
17264
|
|
|
17264
17265
|
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.
|
|
@@ -17671,6 +17672,91 @@ function checkMemoryHealth(options) {
|
|
|
17671
17672
|
};
|
|
17672
17673
|
}
|
|
17673
17674
|
|
|
17675
|
+
/**
|
|
17676
|
+
* HEARTBEAT integration for workspace file size monitoring.
|
|
17677
|
+
*
|
|
17678
|
+
* @remarks
|
|
17679
|
+
* Checks all injected workspace files (AGENTS.md, SOUL.md, TOOLS.md,
|
|
17680
|
+
* MEMORY.md, USER.md) against the OpenClaw ~20,000-char injection limit.
|
|
17681
|
+
* Files exceeding the warning threshold generate HEARTBEAT entries with
|
|
17682
|
+
* trimming guidance.
|
|
17683
|
+
*/
|
|
17684
|
+
/** Workspace files monitored for size budget. */
|
|
17685
|
+
const WORKSPACE_SIZE_FILES = [
|
|
17686
|
+
'AGENTS.md',
|
|
17687
|
+
'SOUL.md',
|
|
17688
|
+
'TOOLS.md',
|
|
17689
|
+
'MEMORY.md',
|
|
17690
|
+
'USER.md',
|
|
17691
|
+
];
|
|
17692
|
+
/** Trimming guidance lines emitted in HEARTBEAT entries. */
|
|
17693
|
+
const TRIMMING_GUIDANCE = [
|
|
17694
|
+
' 1. Move domain-specific content to a local skill',
|
|
17695
|
+
' 2. Extract reference material to companion files with a pointer',
|
|
17696
|
+
' 3. Summarize verbose instructions',
|
|
17697
|
+
' 4. Remove stale content',
|
|
17698
|
+
].join('\n');
|
|
17699
|
+
/**
|
|
17700
|
+
* Check all workspace files against the character budget.
|
|
17701
|
+
*
|
|
17702
|
+
* @param options - Health check options.
|
|
17703
|
+
* @returns Array of results, one per checked file (skips non-existent files
|
|
17704
|
+
* unless they breach the budget, which they cannot by definition).
|
|
17705
|
+
*/
|
|
17706
|
+
function checkWorkspaceFileHealth(options) {
|
|
17707
|
+
const { workspacePath, budgetChars = 20_000, warningThreshold = 0.8, } = options;
|
|
17708
|
+
return WORKSPACE_SIZE_FILES.map((file) => {
|
|
17709
|
+
const filePath = join(workspacePath, file);
|
|
17710
|
+
if (!existsSync(filePath)) {
|
|
17711
|
+
return {
|
|
17712
|
+
file,
|
|
17713
|
+
exists: false,
|
|
17714
|
+
charCount: 0,
|
|
17715
|
+
budget: budgetChars,
|
|
17716
|
+
usage: 0,
|
|
17717
|
+
warning: false,
|
|
17718
|
+
overBudget: false,
|
|
17719
|
+
};
|
|
17720
|
+
}
|
|
17721
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
17722
|
+
const charCount = content.length;
|
|
17723
|
+
const usage = charCount / budgetChars;
|
|
17724
|
+
return {
|
|
17725
|
+
file,
|
|
17726
|
+
exists: true,
|
|
17727
|
+
charCount,
|
|
17728
|
+
budget: budgetChars,
|
|
17729
|
+
usage,
|
|
17730
|
+
warning: usage >= warningThreshold,
|
|
17731
|
+
overBudget: charCount > budgetChars,
|
|
17732
|
+
};
|
|
17733
|
+
});
|
|
17734
|
+
}
|
|
17735
|
+
/**
|
|
17736
|
+
* Convert workspace file health results into HEARTBEAT entries.
|
|
17737
|
+
*
|
|
17738
|
+
* @param results - Results from `checkWorkspaceFileHealth`.
|
|
17739
|
+
* @returns Array of `HeartbeatEntry` objects for files that exceed the
|
|
17740
|
+
* warning threshold.
|
|
17741
|
+
*/
|
|
17742
|
+
function workspaceFileHealthEntries(results) {
|
|
17743
|
+
return results
|
|
17744
|
+
.filter((r) => r.exists && r.warning)
|
|
17745
|
+
.map((r) => {
|
|
17746
|
+
const pct = Math.round(r.usage * 100);
|
|
17747
|
+
const overBudgetNote = r.overBudget ? ' **Over budget.**' : '';
|
|
17748
|
+
const content = [
|
|
17749
|
+
`- Budget: ${r.charCount.toLocaleString()} / ${r.budget.toLocaleString()} chars (${String(pct)}%).${overBudgetNote} Trim to stay under the OpenClaw injection limit.`,
|
|
17750
|
+
`- Suggested trimming priority:\n${TRIMMING_GUIDANCE}`,
|
|
17751
|
+
].join('\n');
|
|
17752
|
+
return {
|
|
17753
|
+
name: r.file,
|
|
17754
|
+
declined: false,
|
|
17755
|
+
content,
|
|
17756
|
+
};
|
|
17757
|
+
});
|
|
17758
|
+
}
|
|
17759
|
+
|
|
17674
17760
|
/**
|
|
17675
17761
|
* Core configuration schema and resolution.
|
|
17676
17762
|
*
|
|
@@ -18120,11 +18206,21 @@ async function runHeartbeatCycle(options) {
|
|
|
18120
18206
|
content: '',
|
|
18121
18207
|
});
|
|
18122
18208
|
}
|
|
18209
|
+
// Workspace file size health check (Decision 70)
|
|
18210
|
+
const wsFileResults = checkWorkspaceFileHealth({ workspacePath });
|
|
18211
|
+
const wsFileAlerts = workspaceFileHealthEntries(wsFileResults);
|
|
18212
|
+
for (const alert of wsFileAlerts) {
|
|
18213
|
+
if (declinedNames.has(alert.name)) {
|
|
18214
|
+
entries.push({ name: alert.name, declined: true, content: '' });
|
|
18215
|
+
}
|
|
18216
|
+
else {
|
|
18217
|
+
entries.push(alert);
|
|
18218
|
+
}
|
|
18219
|
+
}
|
|
18123
18220
|
await writeHeartbeatSection(heartbeatPath, entries);
|
|
18124
18221
|
}
|
|
18125
18222
|
catch (err) {
|
|
18126
|
-
|
|
18127
|
-
console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${msg}`);
|
|
18223
|
+
console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${getErrorMessage(err)}`);
|
|
18128
18224
|
}
|
|
18129
18225
|
}
|
|
18130
18226
|
|
|
@@ -18136,8 +18232,17 @@ async function runHeartbeatCycle(options) {
|
|
|
18136
18232
|
* and platform content maintenance (SOUL.md, AGENTS.md, Platform section)
|
|
18137
18233
|
* on a configurable prime-interval timer cycle.
|
|
18138
18234
|
*/
|
|
18235
|
+
/**
|
|
18236
|
+
* Orchestrates managed content writing for a single Jeeves component.
|
|
18237
|
+
*
|
|
18238
|
+
* @remarks
|
|
18239
|
+
* Created via {@link createComponentWriter}. Manages a timer that fires
|
|
18240
|
+
* at the component's prime-interval, calling `generateToolsContent()`
|
|
18241
|
+
* and `refreshPlatformContent()` on each cycle.
|
|
18242
|
+
*/
|
|
18139
18243
|
class ComponentWriter {
|
|
18140
18244
|
timer;
|
|
18245
|
+
jitterTimeout;
|
|
18141
18246
|
component;
|
|
18142
18247
|
configDir;
|
|
18143
18248
|
gatewayUrl;
|
|
@@ -18152,25 +18257,36 @@ class ComponentWriter {
|
|
|
18152
18257
|
get componentConfigDir() {
|
|
18153
18258
|
return this.configDir;
|
|
18154
18259
|
}
|
|
18155
|
-
/** Whether the writer timer is currently running. */
|
|
18260
|
+
/** Whether the writer timer is currently running or pending its first cycle. */
|
|
18156
18261
|
get isRunning() {
|
|
18157
|
-
return this.timer !== undefined;
|
|
18262
|
+
return this.jitterTimeout !== undefined || this.timer !== undefined;
|
|
18158
18263
|
}
|
|
18159
18264
|
/**
|
|
18160
18265
|
* Start the writer timer.
|
|
18161
18266
|
*
|
|
18162
18267
|
* @remarks
|
|
18163
|
-
*
|
|
18268
|
+
* Delays the first cycle by a random jitter (0 to one full interval) to
|
|
18269
|
+
* spread initial writes across all component plugins and reduce EPERM
|
|
18270
|
+
* contention on startup.
|
|
18164
18271
|
*/
|
|
18165
18272
|
start() {
|
|
18166
|
-
if (this.
|
|
18273
|
+
if (this.isRunning)
|
|
18167
18274
|
return;
|
|
18168
|
-
//
|
|
18169
|
-
|
|
18170
|
-
|
|
18275
|
+
// Random jitter up to one full interval to spread initial writes
|
|
18276
|
+
const intervalMs = this.component.refreshIntervalSeconds * 1000;
|
|
18277
|
+
const jitterMs = Math.floor(Math.random() * intervalMs);
|
|
18278
|
+
this.jitterTimeout = setTimeout(() => {
|
|
18279
|
+
this.jitterTimeout = undefined;
|
|
18280
|
+
void this.cycle();
|
|
18281
|
+
this.timer = setInterval(() => void this.cycle(), intervalMs);
|
|
18282
|
+
}, jitterMs);
|
|
18171
18283
|
}
|
|
18172
18284
|
/** Stop the writer timer. */
|
|
18173
18285
|
stop() {
|
|
18286
|
+
if (this.jitterTimeout) {
|
|
18287
|
+
clearTimeout(this.jitterTimeout);
|
|
18288
|
+
this.jitterTimeout = undefined;
|
|
18289
|
+
}
|
|
18174
18290
|
if (this.timer) {
|
|
18175
18291
|
clearInterval(this.timer);
|
|
18176
18292
|
this.timer = undefined;
|
|
@@ -18227,8 +18343,7 @@ class ComponentWriter {
|
|
|
18227
18343
|
});
|
|
18228
18344
|
}
|
|
18229
18345
|
catch (err) {
|
|
18230
|
-
|
|
18231
|
-
console.warn(`jeeves-core: ComponentWriter cycle failed for ${this.component.name}: ${message}`);
|
|
18346
|
+
console.warn(`jeeves-core: ComponentWriter cycle failed for ${this.component.name}: ${getErrorMessage(err)}`);
|
|
18232
18347
|
}
|
|
18233
18348
|
}
|
|
18234
18349
|
}
|
|
@@ -18538,8 +18653,7 @@ function createPluginToolset(descriptor) {
|
|
|
18538
18653
|
return Promise.resolve(ok({ service: name, action, success: true }));
|
|
18539
18654
|
}
|
|
18540
18655
|
catch (err) {
|
|
18541
|
-
|
|
18542
|
-
return Promise.resolve(fail(`Service ${action} failed: ${msg}`));
|
|
18656
|
+
return Promise.resolve(fail(`Service ${action} failed: ${getErrorMessage(err)}`));
|
|
18543
18657
|
}
|
|
18544
18658
|
},
|
|
18545
18659
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "jeeves-watcher-openclaw",
|
|
3
3
|
"name": "Jeeves Watcher",
|
|
4
4
|
"description": "Semantic search, metadata enrichment, and instance administration for a jeeves-watcher deployment.",
|
|
5
|
-
"version": "0.14.
|
|
5
|
+
"version": "0.14.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
"dist/skills/jeeves-watcher"
|
|
8
8
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-watcher-openclaw",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"author": "Jason Williscroft",
|
|
5
5
|
"description": "OpenClaw plugin for jeeves-watcher — semantic search and metadata enrichment tools",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"hideCredit": true
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@karmaniverous/jeeves": "^0.5.
|
|
60
|
+
"@karmaniverous/jeeves": "^0.5.3"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@dotenvx/dotenvx": "^1.59.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
68
68
|
"auto-changelog": "^2.5.0",
|
|
69
69
|
"cross-env": "^10.1.0",
|
|
70
|
-
"knip": "^6.
|
|
70
|
+
"knip": "^6.3.0",
|
|
71
71
|
"release-it": "^19.2.4",
|
|
72
72
|
"rollup": "^4.60.1",
|
|
73
73
|
"tslib": "^2.8.1",
|