@karmaniverous/jeeves 0.5.7 → 0.5.9
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/README.md +2 -5
- package/content/agents-section.md +1 -1
- package/content/skill.md +1 -1
- package/content/tools-platform.md +34 -4
- package/dist/cli/jeeves/index.js +67 -88
- package/dist/cli/plugin/index.js +4 -3
- package/dist/cli/service/index.js +6 -12
- package/dist/index.d.ts +56 -26
- package/dist/index.js +154 -118
- package/package.json +15 -14
package/README.md
CHANGED
|
@@ -220,8 +220,7 @@ Optional `jeeves.config.json` at the workspace root provides shared defaults for
|
|
|
220
220
|
},
|
|
221
221
|
"memory": {
|
|
222
222
|
"budget": 20000,
|
|
223
|
-
"warningThreshold": 0.8
|
|
224
|
-
"staleDays": 90
|
|
223
|
+
"warningThreshold": 0.8
|
|
225
224
|
}
|
|
226
225
|
}
|
|
227
226
|
```
|
|
@@ -240,10 +239,8 @@ Precedence: **CLI flags → environment variables → `jeeves.config.json` → d
|
|
|
240
239
|
MEMORY.md has a character budget (default: 20,000 characters). The `analyzeMemory()` function tracks:
|
|
241
240
|
|
|
242
241
|
- **Character count and usage percentage** — warns at 80% of budget (configurable via `warningThreshold`)
|
|
243
|
-
- **Stale section detection** — scans ISO dates (`YYYY-MM-DD`) in H2/H3 headings and bullet items; sections whose most recent date exceeds `staleDays` (default: 30) are flagged as stale candidates
|
|
244
|
-
- **Evergreen sections** — sections without parseable dates are never flagged
|
|
245
242
|
|
|
246
|
-
Memory hygiene is reporting-only. Core does not auto-delete content
|
|
243
|
+
Memory hygiene is reporting-only. Core does not auto-delete content (Decision 42). Size pressure is the right signal for curation.
|
|
247
244
|
|
|
248
245
|
## Skill Seeding
|
|
249
246
|
|
|
@@ -37,7 +37,7 @@ Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not
|
|
|
37
37
|
## Platform Surface Conventions
|
|
38
38
|
|
|
39
39
|
**Slack:**
|
|
40
|
-
-
|
|
40
|
+
- Never initiate a threaded reply. Only reply within a thread started by a human.
|
|
41
41
|
- Use `<#C…>` for channel references
|
|
42
42
|
|
|
43
43
|
**Table formatting:** On channels that do not support Markdown tables (Slack, Discord, WhatsApp, IRC), use code-block tables with aligned columns. Markdown tables are only safe in contexts that render them (GitHub, jeeves-server, files).
|
package/content/skill.md
CHANGED
|
@@ -62,7 +62,7 @@ Managed blocks are stationary after initial insertion. Cleanup detection uses Ja
|
|
|
62
62
|
|
|
63
63
|
`jeeves.config.json` at workspace root provides shared defaults:
|
|
64
64
|
- Precedence: CLI flags → env vars → file → defaults
|
|
65
|
-
- Namespaced: `core.*` (workspace, configRoot, gatewayUrl) and `memory.*` (budget, warningThreshold
|
|
65
|
+
- Namespaced: `core.*` (workspace, configRoot, gatewayUrl, devRepos) and `memory.*` (budget, warningThreshold)
|
|
66
66
|
- Inspect with `jeeves config [jsonpath]`
|
|
67
67
|
|
|
68
68
|
## HEARTBEAT Protocol
|
|
@@ -44,8 +44,8 @@ Never manually edit `~/.openclaw/extensions/`. Always use the CLI commands above
|
|
|
44
44
|
|
|
45
45
|
### Reference Templates
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
Reference templates are available at `
|
|
47
|
+
{{#if templatePath}}
|
|
48
|
+
Reference templates are available at `{{templatePath}}`:
|
|
49
49
|
|
|
50
50
|
| Template | Purpose |
|
|
51
51
|
|----------|---------|
|
|
@@ -53,6 +53,36 @@ Reference templates are available at `__TEMPLATE_PATH__`:
|
|
|
53
53
|
| `spec-to-code-guide.md` | The spec-to-code development practice — 7-stage iterative process, convergence loops, release gates |
|
|
54
54
|
|
|
55
55
|
Read these templates when creating new specs, onboarding to new projects, or when asked about the development process.
|
|
56
|
-
|
|
56
|
+
{{else}}
|
|
57
57
|
> Reference templates not yet installed. Run `npx @karmaniverous/jeeves install` to seed templates.
|
|
58
|
-
|
|
58
|
+
{{/if}}
|
|
59
|
+
|
|
60
|
+
### Post-Upgrade Maintenance
|
|
61
|
+
|
|
62
|
+
After updating OpenClaw (`npm install -g openclaw@latest` or equivalent), reinstall all Jeeves component plugins to repair install state:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx @karmaniverous/jeeves install
|
|
66
|
+
npx @karmaniverous/jeeves-runner-openclaw install
|
|
67
|
+
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
68
|
+
npx @karmaniverous/jeeves-server-openclaw install
|
|
69
|
+
npx @karmaniverous/jeeves-meta-openclaw install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then restart the gateway. Plugin installers copy dist files and patch config; reinstalling after an OpenClaw update ensures the extensions directory stays consistent.
|
|
73
|
+
|
|
74
|
+
### Source Code Preference
|
|
75
|
+
|
|
76
|
+
{{#if devRepos}}
|
|
77
|
+
When investigating, debugging, or analyzing Jeeves components, always read TypeScript source from dev repos — never compiled `dist/` from the global npm install. Dev repos:
|
|
78
|
+
|
|
79
|
+
| Component | Dev Repo |
|
|
80
|
+
|-----------|----------|
|
|
81
|
+
{{#each devRepos}}
|
|
82
|
+
| {{@key}} | `{{this}}` |
|
|
83
|
+
{{/each}}
|
|
84
|
+
|
|
85
|
+
Built code is minified, harder to reason about, and wastes context. Always `git pull` before analysis.
|
|
86
|
+
{{else}}
|
|
87
|
+
> Dev repo paths not configured. Add `core.devRepos` to `jeeves.config.json` to enable source code preference guidance.
|
|
88
|
+
{{/if}}
|
package/dist/cli/jeeves/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { execSync, spawnSync } from 'node:child_process';
|
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
10
|
import { lock } from 'proper-lockfile';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import Handlebars from 'handlebars';
|
|
12
13
|
import { packageDirectorySync } from 'package-directory';
|
|
13
14
|
|
|
14
15
|
function getDefaultExportFromCjs (x) {
|
|
@@ -269,14 +270,14 @@ const PLATFORM_COMPONENTS = [
|
|
|
269
270
|
* Core library version, inlined at build time.
|
|
270
271
|
*
|
|
271
272
|
* @remarks
|
|
272
|
-
* The `0.5.
|
|
273
|
+
* The `0.5.8` placeholder is replaced by
|
|
273
274
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
274
275
|
* from `package.json`. This ensures the correct version survives
|
|
275
276
|
* when consumers bundle core into their own dist (where runtime
|
|
276
277
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
277
278
|
*/
|
|
278
279
|
/** The core library version from package.json (inlined at build time). */
|
|
279
|
-
const CORE_VERSION = '0.5.
|
|
280
|
+
const CORE_VERSION = '0.5.8';
|
|
280
281
|
|
|
281
282
|
/**
|
|
282
283
|
* Runtime Node.js version floor check.
|
|
@@ -333,6 +334,11 @@ const workspaceCoreConfigSchema = z
|
|
|
333
334
|
configRoot: z.string().optional().describe('Platform config root path'),
|
|
334
335
|
/** OpenClaw gateway URL. */
|
|
335
336
|
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
337
|
+
/** Dev repo paths keyed by component name. */
|
|
338
|
+
devRepos: z
|
|
339
|
+
.record(z.string(), z.string())
|
|
340
|
+
.optional()
|
|
341
|
+
.describe('Dev repo paths by component name'),
|
|
336
342
|
})
|
|
337
343
|
.partial();
|
|
338
344
|
/** Memory shared config section. */
|
|
@@ -347,13 +353,6 @@ const workspaceMemoryConfigSchema = z
|
|
|
347
353
|
.max(1)
|
|
348
354
|
.optional()
|
|
349
355
|
.describe('Memory warning threshold'),
|
|
350
|
-
/** Staleness threshold in days. */
|
|
351
|
-
staleDays: z
|
|
352
|
-
.number()
|
|
353
|
-
.int()
|
|
354
|
-
.positive()
|
|
355
|
-
.optional()
|
|
356
|
-
.describe('Memory staleness threshold in days'),
|
|
357
356
|
})
|
|
358
357
|
.partial();
|
|
359
358
|
/** Workspace config Zod schema. */
|
|
@@ -381,7 +380,6 @@ const WORKSPACE_CONFIG_DEFAULTS = {
|
|
|
381
380
|
memory: {
|
|
382
381
|
budget: 20_000,
|
|
383
382
|
warningThreshold: 0.8,
|
|
384
|
-
staleDays: 30,
|
|
385
383
|
},
|
|
386
384
|
};
|
|
387
385
|
/**
|
|
@@ -444,6 +442,7 @@ function init(options) {
|
|
|
444
442
|
workspacePath: options.workspacePath,
|
|
445
443
|
configRoot: options.configRoot,
|
|
446
444
|
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
445
|
+
componentConfigPaths: new Map(),
|
|
447
446
|
};
|
|
448
447
|
}
|
|
449
448
|
/**
|
|
@@ -511,7 +510,6 @@ function resolveCliConfig(opts) {
|
|
|
511
510
|
memory: {
|
|
512
511
|
budget: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_BUDGET'), fileConfig?.memory?.budget, WORKSPACE_CONFIG_DEFAULTS.memory.budget),
|
|
513
512
|
warningThreshold: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_WARNING_THRESHOLD'), fileConfig?.memory?.warningThreshold, WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold),
|
|
514
|
-
staleDays: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_STALE_DAYS'), fileConfig?.memory?.staleDays, WORKSPACE_CONFIG_DEFAULTS.memory.staleDays),
|
|
515
513
|
},
|
|
516
514
|
};
|
|
517
515
|
}
|
|
@@ -820,9 +818,8 @@ function getServiceUrl(serviceName, consumerName) {
|
|
|
820
818
|
if (coreUrl)
|
|
821
819
|
return coreUrl;
|
|
822
820
|
// 3. Fall back to port constants
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return `http://127.0.0.1:${String(port)}`;
|
|
821
|
+
if (serviceName in DEFAULT_PORTS) {
|
|
822
|
+
return `http://127.0.0.1:${String(DEFAULT_PORTS[serviceName])}`;
|
|
826
823
|
}
|
|
827
824
|
throw new Error(`jeeves-core: unknown service "${serviceName}" and no config found`);
|
|
828
825
|
}
|
|
@@ -1177,7 +1174,7 @@ Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not
|
|
|
1177
1174
|
## Platform Surface Conventions
|
|
1178
1175
|
|
|
1179
1176
|
**Slack:**
|
|
1180
|
-
-
|
|
1177
|
+
- Never initiate a threaded reply. Only reply within a thread started by a human.
|
|
1181
1178
|
- Use \`<#C…>\` for channel references
|
|
1182
1179
|
|
|
1183
1180
|
**Table formatting:** On channels that do not support Markdown tables (Slack, Discord, WhatsApp, IRC), use code-block tables with aligned columns. Markdown tables are only safe in contexts that render them (GitHub, jeeves-server, files).
|
|
@@ -1383,8 +1380,8 @@ Never manually edit \`~/.openclaw/extensions/\`. Always use the CLI commands abo
|
|
|
1383
1380
|
|
|
1384
1381
|
### Reference Templates
|
|
1385
1382
|
|
|
1386
|
-
|
|
1387
|
-
Reference templates are available at \`
|
|
1383
|
+
{{#if templatePath}}
|
|
1384
|
+
Reference templates are available at \`{{templatePath}}\`:
|
|
1388
1385
|
|
|
1389
1386
|
| Template | Purpose |
|
|
1390
1387
|
|----------|---------|
|
|
@@ -1392,9 +1389,39 @@ Reference templates are available at \`__TEMPLATE_PATH__\`:
|
|
|
1392
1389
|
| \`spec-to-code-guide.md\` | The spec-to-code development practice — 7-stage iterative process, convergence loops, release gates |
|
|
1393
1390
|
|
|
1394
1391
|
Read these templates when creating new specs, onboarding to new projects, or when asked about the development process.
|
|
1395
|
-
|
|
1392
|
+
{{else}}
|
|
1396
1393
|
> Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
|
|
1397
|
-
|
|
1394
|
+
{{/if}}
|
|
1395
|
+
|
|
1396
|
+
### Post-Upgrade Maintenance
|
|
1397
|
+
|
|
1398
|
+
After updating OpenClaw (\`npm install -g openclaw@latest\` or equivalent), reinstall all Jeeves component plugins to repair install state:
|
|
1399
|
+
|
|
1400
|
+
\`\`\`bash
|
|
1401
|
+
npx @karmaniverous/jeeves install
|
|
1402
|
+
npx @karmaniverous/jeeves-runner-openclaw install
|
|
1403
|
+
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
1404
|
+
npx @karmaniverous/jeeves-server-openclaw install
|
|
1405
|
+
npx @karmaniverous/jeeves-meta-openclaw install
|
|
1406
|
+
\`\`\`
|
|
1407
|
+
|
|
1408
|
+
Then restart the gateway. Plugin installers copy dist files and patch config; reinstalling after an OpenClaw update ensures the extensions directory stays consistent.
|
|
1409
|
+
|
|
1410
|
+
### Source Code Preference
|
|
1411
|
+
|
|
1412
|
+
{{#if devRepos}}
|
|
1413
|
+
When investigating, debugging, or analyzing Jeeves components, always read TypeScript source from dev repos — never compiled \`dist/\` from the global npm install. Dev repos:
|
|
1414
|
+
|
|
1415
|
+
| Component | Dev Repo |
|
|
1416
|
+
|-----------|----------|
|
|
1417
|
+
{{#each devRepos}}
|
|
1418
|
+
| {{@key}} | \`{{this}}\` |
|
|
1419
|
+
{{/each}}
|
|
1420
|
+
|
|
1421
|
+
Built code is minified, harder to reason about, and wastes context. Always \`git pull\` before analysis.
|
|
1422
|
+
{{else}}
|
|
1423
|
+
> Dev repo paths not configured. Add \`core.devRepos\` to \`jeeves.config.json\` to enable source code preference guidance.
|
|
1424
|
+
{{/if}}
|
|
1398
1425
|
`;
|
|
1399
1426
|
|
|
1400
1427
|
/**
|
|
@@ -1843,6 +1870,10 @@ async function updateManagedSection(filePath, content, options = {}) {
|
|
|
1843
1870
|
* Platform template with live data, and writes managed sections using
|
|
1844
1871
|
* `updateManagedSection`.
|
|
1845
1872
|
*/
|
|
1873
|
+
/** Compiled Handlebars template for the Platform section (cached at module level). */
|
|
1874
|
+
const compiledPlatformTemplate = Handlebars.compile(toolsPlatformTemplate, {
|
|
1875
|
+
noEscape: true,
|
|
1876
|
+
});
|
|
1846
1877
|
/**
|
|
1847
1878
|
* Resolve the package's content directory for template file copying.
|
|
1848
1879
|
*
|
|
@@ -1886,23 +1917,13 @@ function copyTemplates(coreConfigDir) {
|
|
|
1886
1917
|
cpSync(sourceDir, destDir, { recursive: true });
|
|
1887
1918
|
}
|
|
1888
1919
|
/**
|
|
1889
|
-
* Render the Platform template using
|
|
1920
|
+
* Render the Platform template using Handlebars.
|
|
1890
1921
|
*
|
|
1891
|
-
* @param
|
|
1922
|
+
* @param context - Template context with optional templatePath and devRepos.
|
|
1892
1923
|
* @returns Rendered platform content string.
|
|
1893
1924
|
*/
|
|
1894
|
-
function renderPlatformTemplate(
|
|
1895
|
-
|
|
1896
|
-
let content = toolsPlatformTemplate;
|
|
1897
|
-
// Handle <!-- IF_TEMPLATES --> ... <!-- ELSE_TEMPLATES --> ... <!-- ENDIF_TEMPLATES --> block
|
|
1898
|
-
const ifRegex = /<!-- IF_TEMPLATES -->([\s\S]*?)<!-- ELSE_TEMPLATES -->([\s\S]*?)<!-- ENDIF_TEMPLATES -->/;
|
|
1899
|
-
const match = ifRegex.exec(content);
|
|
1900
|
-
if (match) {
|
|
1901
|
-
content = content.replace(match[0], templatesAvailable ? match[1] : match[2]);
|
|
1902
|
-
}
|
|
1903
|
-
// Replace __TEMPLATE_PATH__ with the actual path
|
|
1904
|
-
content = content.replace(/__TEMPLATE_PATH__/g, templatePath);
|
|
1905
|
-
return content;
|
|
1925
|
+
function renderPlatformTemplate(context) {
|
|
1926
|
+
return compiledPlatformTemplate(context);
|
|
1906
1927
|
}
|
|
1907
1928
|
/**
|
|
1908
1929
|
* Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
@@ -1910,7 +1931,7 @@ function renderPlatformTemplate(templatePath) {
|
|
|
1910
1931
|
* @param options - Configuration for the refresh cycle.
|
|
1911
1932
|
*/
|
|
1912
1933
|
async function refreshPlatformContent(options) {
|
|
1913
|
-
const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, } = options;
|
|
1934
|
+
const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, workspaceConfig, } = options;
|
|
1914
1935
|
const workspacePath = getWorkspacePath();
|
|
1915
1936
|
const coreConfigDir = getCoreConfigDir();
|
|
1916
1937
|
// 1. Write calling component's version entry
|
|
@@ -1924,7 +1945,11 @@ async function refreshPlatformContent(options) {
|
|
|
1924
1945
|
}
|
|
1925
1946
|
// 2. Render Platform template
|
|
1926
1947
|
const templatePath = join(coreConfigDir, TEMPLATES_DIR);
|
|
1927
|
-
const
|
|
1948
|
+
const wsConfig = workspaceConfig ?? loadWorkspaceConfig(workspacePath);
|
|
1949
|
+
const platformContent = renderPlatformTemplate({
|
|
1950
|
+
templatePath: existsSync(templatePath) ? templatePath : undefined,
|
|
1951
|
+
devRepos: wsConfig?.core?.devRepos,
|
|
1952
|
+
});
|
|
1928
1953
|
// 3. Write TOOLS.md Platform section
|
|
1929
1954
|
const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
|
|
1930
1955
|
await updateManagedSection(toolsPath, platformContent, {
|
|
@@ -2018,7 +2043,7 @@ Managed blocks are stationary after initial insertion. Cleanup detection uses Ja
|
|
|
2018
2043
|
|
|
2019
2044
|
\`jeeves.config.json\` at workspace root provides shared defaults:
|
|
2020
2045
|
- Precedence: CLI flags → env vars → file → defaults
|
|
2021
|
-
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold
|
|
2046
|
+
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl, devRepos) and \`memory.*\` (budget, warningThreshold)
|
|
2022
2047
|
- Inspect with \`jeeves config [jsonpath]\`
|
|
2023
2048
|
|
|
2024
2049
|
## HEARTBEAT Protocol
|
|
@@ -2202,46 +2227,21 @@ function registerInstallCommand(program) {
|
|
|
2202
2227
|
}
|
|
2203
2228
|
|
|
2204
2229
|
/**
|
|
2205
|
-
* Memory budget accounting
|
|
2230
|
+
* Memory budget accounting for MEMORY.md.
|
|
2206
2231
|
*
|
|
2207
2232
|
* @remarks
|
|
2208
|
-
*
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
2211
|
-
* human- or agent-mediated (Decision 42).
|
|
2233
|
+
* Reports character count against a configured budget and warning threshold
|
|
2234
|
+
* state. Does not auto-delete: review remains human- or agent-mediated
|
|
2235
|
+
* (Decision 42). Staleness detection removed in v0.5.9 (Decision 45).
|
|
2212
2236
|
*/
|
|
2213
|
-
/** ISO date pattern: YYYY-MM-DD. */
|
|
2214
|
-
const ISO_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/g;
|
|
2215
|
-
/** H2 heading pattern used to split sections. */
|
|
2216
|
-
const H2_RE = /^## /m;
|
|
2217
2237
|
/**
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
* @param text - Text to scan for dates.
|
|
2221
|
-
* @returns The most recent date found, or undefined.
|
|
2222
|
-
*/
|
|
2223
|
-
function extractMostRecentDate(text) {
|
|
2224
|
-
const matches = text.match(ISO_DATE_RE);
|
|
2225
|
-
if (!matches)
|
|
2226
|
-
return undefined;
|
|
2227
|
-
let latest;
|
|
2228
|
-
for (const match of matches) {
|
|
2229
|
-
const d = new Date(match + 'T00:00:00Z');
|
|
2230
|
-
if (!Number.isNaN(d.getTime())) {
|
|
2231
|
-
if (!latest || d > latest)
|
|
2232
|
-
latest = d;
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
return latest;
|
|
2236
|
-
}
|
|
2237
|
-
/**
|
|
2238
|
-
* Analyze MEMORY.md for budget and staleness.
|
|
2238
|
+
* Analyze MEMORY.md for budget health.
|
|
2239
2239
|
*
|
|
2240
2240
|
* @param options - Analysis configuration.
|
|
2241
2241
|
* @returns Memory hygiene result.
|
|
2242
2242
|
*/
|
|
2243
2243
|
function analyzeMemory(options) {
|
|
2244
|
-
const { workspacePath, budget, warningThreshold
|
|
2244
|
+
const { workspacePath, budget, warningThreshold } = options;
|
|
2245
2245
|
const memoryPath = join(workspacePath, WORKSPACE_FILES.memory);
|
|
2246
2246
|
if (!existsSync(memoryPath)) {
|
|
2247
2247
|
return {
|
|
@@ -2251,8 +2251,6 @@ function analyzeMemory(options) {
|
|
|
2251
2251
|
usage: 0,
|
|
2252
2252
|
warning: false,
|
|
2253
2253
|
overBudget: false,
|
|
2254
|
-
staleCandidates: 0,
|
|
2255
|
-
staleSectionNames: [],
|
|
2256
2254
|
};
|
|
2257
2255
|
}
|
|
2258
2256
|
const content = readFileSync(memoryPath, 'utf-8');
|
|
@@ -2260,21 +2258,6 @@ function analyzeMemory(options) {
|
|
|
2260
2258
|
const usage = budget > 0 ? charCount / budget : charCount > 0 ? Infinity : 0;
|
|
2261
2259
|
const warning = usage >= warningThreshold;
|
|
2262
2260
|
const overBudget = usage > 1;
|
|
2263
|
-
// Split into H2 sections and scan for staleness
|
|
2264
|
-
const sections = content.split(H2_RE).slice(1); // skip content before first H2
|
|
2265
|
-
const now = Date.now();
|
|
2266
|
-
const thresholdMs = staleDays * 24 * 60 * 60 * 1000;
|
|
2267
|
-
const staleSectionNames = [];
|
|
2268
|
-
for (const section of sections) {
|
|
2269
|
-
const sectionName = section.split('\n')[0]?.trim() ?? '';
|
|
2270
|
-
const recentDate = extractMostRecentDate(section);
|
|
2271
|
-
// Sections without dates are evergreen — never flagged (Decision 47)
|
|
2272
|
-
if (!recentDate)
|
|
2273
|
-
continue;
|
|
2274
|
-
if (now - recentDate.getTime() > thresholdMs) {
|
|
2275
|
-
staleSectionNames.push(sectionName);
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
2261
|
return {
|
|
2279
2262
|
exists: true,
|
|
2280
2263
|
charCount,
|
|
@@ -2282,8 +2265,6 @@ function analyzeMemory(options) {
|
|
|
2282
2265
|
usage,
|
|
2283
2266
|
warning,
|
|
2284
2267
|
overBudget,
|
|
2285
|
-
staleCandidates: staleSectionNames.length,
|
|
2286
|
-
staleSectionNames,
|
|
2287
2268
|
};
|
|
2288
2269
|
}
|
|
2289
2270
|
|
|
@@ -2382,7 +2363,6 @@ function registerStatusCommand(program) {
|
|
|
2382
2363
|
workspacePath: getWorkspacePath(),
|
|
2383
2364
|
budget: resolved.memory.budget.value,
|
|
2384
2365
|
warningThreshold: resolved.memory.warningThreshold.value,
|
|
2385
|
-
staleDays: resolved.memory.staleDays.value,
|
|
2386
2366
|
});
|
|
2387
2367
|
console.log('Memory hygiene');
|
|
2388
2368
|
console.log('-'.repeat(60));
|
|
@@ -2397,7 +2377,6 @@ function registerStatusCommand(program) {
|
|
|
2397
2377
|
? '⚠ Warning'
|
|
2398
2378
|
: '✅ OK';
|
|
2399
2379
|
console.log(`Chars: ${String(memory.charCount)} / ${String(memory.budget)} (${String(usagePct)}%) — ${status}`);
|
|
2400
|
-
console.log(`Stale candidates: ${String(memory.staleCandidates)}`);
|
|
2401
2380
|
}
|
|
2402
2381
|
console.log();
|
|
2403
2382
|
if (!allHealthy) {
|
package/dist/cli/plugin/index.js
CHANGED
|
@@ -130,14 +130,14 @@ const COMPONENT_VERSIONS_FILE = 'component-versions.json';
|
|
|
130
130
|
* Core library version, inlined at build time.
|
|
131
131
|
*
|
|
132
132
|
* @remarks
|
|
133
|
-
* The `0.5.
|
|
133
|
+
* The `0.5.8` placeholder is replaced by
|
|
134
134
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
135
135
|
* from `package.json`. This ensures the correct version survives
|
|
136
136
|
* when consumers bundle core into their own dist (where runtime
|
|
137
137
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
138
138
|
*/
|
|
139
139
|
/** The core library version from package.json (inlined at build time). */
|
|
140
|
-
const CORE_VERSION = '0.5.
|
|
140
|
+
const CORE_VERSION = '0.5.8';
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* Shared file I/O helpers for managed section operations.
|
|
@@ -386,6 +386,7 @@ function init(options) {
|
|
|
386
386
|
workspacePath: options.workspacePath,
|
|
387
387
|
configRoot: options.configRoot,
|
|
388
388
|
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
389
|
+
componentConfigPaths: new Map(),
|
|
389
390
|
};
|
|
390
391
|
}
|
|
391
392
|
/**
|
|
@@ -814,7 +815,7 @@ Managed blocks are stationary after initial insertion. Cleanup detection uses Ja
|
|
|
814
815
|
|
|
815
816
|
\`jeeves.config.json\` at workspace root provides shared defaults:
|
|
816
817
|
- Precedence: CLI flags → env vars → file → defaults
|
|
817
|
-
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold
|
|
818
|
+
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl, devRepos) and \`memory.*\` (budget, warningThreshold)
|
|
818
819
|
- Inspect with \`jeeves config [jsonpath]\`
|
|
819
820
|
|
|
820
821
|
## HEARTBEAT Protocol
|
|
@@ -261,6 +261,7 @@ function init(options) {
|
|
|
261
261
|
workspacePath: options.workspacePath,
|
|
262
262
|
configRoot: options.configRoot,
|
|
263
263
|
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
264
|
+
componentConfigPaths: new Map(),
|
|
264
265
|
};
|
|
265
266
|
}
|
|
266
267
|
/**
|
|
@@ -730,6 +731,11 @@ const workspaceCoreConfigSchema = z
|
|
|
730
731
|
configRoot: z.string().optional().describe('Platform config root path'),
|
|
731
732
|
/** OpenClaw gateway URL. */
|
|
732
733
|
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
734
|
+
/** Dev repo paths keyed by component name. */
|
|
735
|
+
devRepos: z
|
|
736
|
+
.record(z.string(), z.string())
|
|
737
|
+
.optional()
|
|
738
|
+
.describe('Dev repo paths by component name'),
|
|
733
739
|
})
|
|
734
740
|
.partial();
|
|
735
741
|
/** Memory shared config section. */
|
|
@@ -744,13 +750,6 @@ const workspaceMemoryConfigSchema = z
|
|
|
744
750
|
.max(1)
|
|
745
751
|
.optional()
|
|
746
752
|
.describe('Memory warning threshold'),
|
|
747
|
-
/** Staleness threshold in days. */
|
|
748
|
-
staleDays: z
|
|
749
|
-
.number()
|
|
750
|
-
.int()
|
|
751
|
-
.positive()
|
|
752
|
-
.optional()
|
|
753
|
-
.describe('Memory staleness threshold in days'),
|
|
754
753
|
})
|
|
755
754
|
.partial();
|
|
756
755
|
/** Workspace config Zod schema. */
|
|
@@ -1028,11 +1027,6 @@ function createServiceCli(descriptor) {
|
|
|
1028
1027
|
});
|
|
1029
1028
|
// Apply custom CLI commands if provided
|
|
1030
1029
|
if (descriptor.customCliCommands) {
|
|
1031
|
-
// Cast required: @commander-js/extra-typings Command has generic type
|
|
1032
|
-
// parameters that don't align with the descriptor's base Command type.
|
|
1033
|
-
// The descriptor can't know the parent Command's exact generic parameters
|
|
1034
|
-
// at definition time. The cast is safe — customCliCommands only adds
|
|
1035
|
-
// subcommands and doesn't depend on the parent's generic state.
|
|
1036
1030
|
descriptor.customCliCommands(program);
|
|
1037
1031
|
}
|
|
1038
1032
|
return program;
|
package/dist/index.d.ts
CHANGED
|
@@ -295,11 +295,11 @@ declare const workspaceConfigSchema: z.ZodObject<{
|
|
|
295
295
|
workspace: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
296
296
|
configRoot: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
297
297
|
gatewayUrl: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
298
|
+
devRepos: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
298
299
|
}, z.core.$strip>>;
|
|
299
300
|
memory: z.ZodOptional<z.ZodObject<{
|
|
300
301
|
budget: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
301
302
|
warningThreshold: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
302
|
-
staleDays: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
303
303
|
}, z.core.$strip>>;
|
|
304
304
|
}, z.core.$strip>;
|
|
305
305
|
/** Workspace config type. */
|
|
@@ -327,8 +327,6 @@ declare const WORKSPACE_CONFIG_DEFAULTS: {
|
|
|
327
327
|
readonly budget: 20_000;
|
|
328
328
|
/** Default warning threshold as a fraction of budget (80%). */
|
|
329
329
|
readonly warningThreshold: 0.8;
|
|
330
|
-
/** Default staleness threshold in days. */
|
|
331
|
-
readonly staleDays: 30;
|
|
332
330
|
};
|
|
333
331
|
};
|
|
334
332
|
/** Provenance source for a resolved config value. */
|
|
@@ -396,8 +394,6 @@ interface ResolvedCliConfig {
|
|
|
396
394
|
budget: ResolvedValue<number>;
|
|
397
395
|
/** Resolved warning threshold as a fraction of budget. */
|
|
398
396
|
warningThreshold: ResolvedValue<number>;
|
|
399
|
-
/** Resolved staleness threshold in days. */
|
|
400
|
-
staleDays: ResolvedValue<number>;
|
|
401
397
|
};
|
|
402
398
|
}
|
|
403
399
|
|
|
@@ -576,6 +572,7 @@ declare class ComponentWriter {
|
|
|
576
572
|
private readonly gatewayUrl;
|
|
577
573
|
private readonly pendingCleanups;
|
|
578
574
|
private cyclePromise;
|
|
575
|
+
private stopped;
|
|
579
576
|
/** @internal */
|
|
580
577
|
constructor(component: JeevesComponentDescriptor, options?: ComponentWriterOptions);
|
|
581
578
|
/** The component's config directory path. */
|
|
@@ -652,7 +649,8 @@ interface AsyncContentCacheOptions {
|
|
|
652
649
|
placeholder?: string;
|
|
653
650
|
/**
|
|
654
651
|
* Optional error handler. Called when `fetch` throws.
|
|
655
|
-
* Defaults to
|
|
652
|
+
* Defaults to a handler that logs transient network errors as
|
|
653
|
+
* concise warnings and unexpected errors with full details.
|
|
656
654
|
*/
|
|
657
655
|
onError?: (error: unknown) => void;
|
|
658
656
|
}
|
|
@@ -1111,6 +1109,28 @@ declare function getCoreConfigFile(): string;
|
|
|
1111
1109
|
* @throws Error if `init()` has not been called.
|
|
1112
1110
|
*/
|
|
1113
1111
|
declare function getComponentConfigDir(componentName: string): string;
|
|
1112
|
+
/**
|
|
1113
|
+
* Register the actual config file path for a component.
|
|
1114
|
+
*
|
|
1115
|
+
* @remarks
|
|
1116
|
+
* Call this after resolving the `--config` CLI argument so that
|
|
1117
|
+
* `getComponentConfigPath` returns the real file location instead of
|
|
1118
|
+
* the default derived from `configRoot`.
|
|
1119
|
+
*
|
|
1120
|
+
* @param componentName - The component name (e.g., 'watcher', 'runner').
|
|
1121
|
+
* @param absolutePath - Absolute path to the component's config file.
|
|
1122
|
+
* @throws Error if `init()` has not been called.
|
|
1123
|
+
*/
|
|
1124
|
+
declare function registerComponentConfigPath(componentName: string, absolutePath: string): void;
|
|
1125
|
+
/**
|
|
1126
|
+
* Get the registered config file path for a component, or `undefined`
|
|
1127
|
+
* if no override has been registered.
|
|
1128
|
+
*
|
|
1129
|
+
* @param componentName - The component name.
|
|
1130
|
+
* @returns The registered absolute path, or `undefined`.
|
|
1131
|
+
* @throws Error if `init()` has not been called.
|
|
1132
|
+
*/
|
|
1133
|
+
declare function getComponentConfigPath(componentName: string): string | undefined;
|
|
1114
1134
|
/**
|
|
1115
1135
|
* Reset initialization state. Used for testing only.
|
|
1116
1136
|
*/
|
|
@@ -1332,13 +1352,12 @@ declare function formatEndMarker(markerText: string): string;
|
|
|
1332
1352
|
declare function shouldWrite(myVersion: string, existing: VersionStamp | undefined, stalenessThresholdMs?: number): boolean;
|
|
1333
1353
|
|
|
1334
1354
|
/**
|
|
1335
|
-
* Memory budget accounting
|
|
1355
|
+
* Memory budget accounting for MEMORY.md.
|
|
1336
1356
|
*
|
|
1337
1357
|
* @remarks
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
* human- or agent-mediated (Decision 42).
|
|
1358
|
+
* Reports character count against a configured budget and warning threshold
|
|
1359
|
+
* state. Does not auto-delete: review remains human- or agent-mediated
|
|
1360
|
+
* (Decision 42). Staleness detection removed in v0.5.9 (Decision 45).
|
|
1342
1361
|
*/
|
|
1343
1362
|
/** Result of memory hygiene analysis. */
|
|
1344
1363
|
interface MemoryHygieneResult {
|
|
@@ -1354,10 +1373,6 @@ interface MemoryHygieneResult {
|
|
|
1354
1373
|
warning: boolean;
|
|
1355
1374
|
/** Whether usage exceeds the budget. */
|
|
1356
1375
|
overBudget: boolean;
|
|
1357
|
-
/** Number of H2 sections flagged as stale candidates. */
|
|
1358
|
-
staleCandidates: number;
|
|
1359
|
-
/** Names of stale sections. */
|
|
1360
|
-
staleSectionNames: string[];
|
|
1361
1376
|
}
|
|
1362
1377
|
/** Options for memory hygiene analysis. */
|
|
1363
1378
|
interface MemoryHygieneOptions {
|
|
@@ -1367,18 +1382,9 @@ interface MemoryHygieneOptions {
|
|
|
1367
1382
|
budget: number;
|
|
1368
1383
|
/** Warning threshold as a fraction of budget (0–1). */
|
|
1369
1384
|
warningThreshold: number;
|
|
1370
|
-
/** Staleness threshold in days. */
|
|
1371
|
-
staleDays: number;
|
|
1372
1385
|
}
|
|
1373
1386
|
/**
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
* @param text - Text to scan for dates.
|
|
1377
|
-
* @returns The most recent date found, or undefined.
|
|
1378
|
-
*/
|
|
1379
|
-
declare function extractMostRecentDate(text: string): Date | undefined;
|
|
1380
|
-
/**
|
|
1381
|
-
* Analyze MEMORY.md for budget and staleness.
|
|
1387
|
+
* Analyze MEMORY.md for budget health.
|
|
1382
1388
|
*
|
|
1383
1389
|
* @param options - Analysis configuration.
|
|
1384
1390
|
* @returns Memory hygiene result.
|
|
@@ -1416,6 +1422,7 @@ declare function checkMemoryHealth(options: MemoryHygieneOptions): HeartbeatEntr
|
|
|
1416
1422
|
* Platform template with live data, and writes managed sections using
|
|
1417
1423
|
* `updateManagedSection`.
|
|
1418
1424
|
*/
|
|
1425
|
+
|
|
1419
1426
|
/** Options for refreshPlatformContent. */
|
|
1420
1427
|
interface RefreshPlatformContentOptions {
|
|
1421
1428
|
/** Core library version for version-stamp convergence. */
|
|
@@ -1430,6 +1437,8 @@ interface RefreshPlatformContentOptions {
|
|
|
1430
1437
|
pluginPackage?: string;
|
|
1431
1438
|
/** Staleness threshold override in ms. */
|
|
1432
1439
|
stalenessThresholdMs?: number;
|
|
1440
|
+
/** Pre-loaded workspace config (avoids redundant reads when caller already loaded it). */
|
|
1441
|
+
workspaceConfig?: WorkspaceConfig;
|
|
1433
1442
|
}
|
|
1434
1443
|
/**
|
|
1435
1444
|
* Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
@@ -1895,5 +1904,26 @@ interface ServiceManager {
|
|
|
1895
1904
|
*/
|
|
1896
1905
|
declare function createServiceManager(descriptor: JeevesComponentDescriptor): ServiceManager;
|
|
1897
1906
|
|
|
1898
|
-
|
|
1907
|
+
/**
|
|
1908
|
+
* Shared internal utility functions.
|
|
1909
|
+
*
|
|
1910
|
+
* @packageDocumentation
|
|
1911
|
+
*/
|
|
1912
|
+
/**
|
|
1913
|
+
* Extract a human-readable message from an unknown caught value.
|
|
1914
|
+
*
|
|
1915
|
+
* @param err - The caught value (typically `unknown`).
|
|
1916
|
+
* @returns The error message string.
|
|
1917
|
+
*/
|
|
1918
|
+
declare function getErrorMessage(err: unknown): string;
|
|
1919
|
+
/**
|
|
1920
|
+
* Classify whether an error is a transient network failure.
|
|
1921
|
+
*
|
|
1922
|
+
* @param err - The caught value.
|
|
1923
|
+
* @returns `true` for ECONNRESET, ETIMEDOUT, UND_ERR_CONNECT_TIMEOUT,
|
|
1924
|
+
* AbortError, and timeout-related fetch errors.
|
|
1925
|
+
*/
|
|
1926
|
+
declare function isTransientError(err: unknown): boolean;
|
|
1927
|
+
|
|
1928
|
+
export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, shingles, shouldWrite, sleepAsync, sleepMs, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
|
|
1899
1929
|
export type { AccountConfig, AsyncContentCacheOptions, ComponentDependencies, ComponentState, ComponentVersionEntry, ComponentVersionsState, ComponentWriterOptions, ConfigApplyHandler, ConfigApplyRequest, ConfigApplyResult, ConfigProvenance, ConfigQueryHandler, ConfigQueryResponse, CoreConfig, CreatePluginCliOptions, CreateStatusHandlerOptions, GoogleAuthOptions, HeartbeatEntry, InitOptions, JeevesComponentDescriptor, ManagedMarkers, ManagedSection, MemoryHygieneOptions, MemoryHygieneResult, OrchestrateHeartbeatOptions, ParseManagedResult, ParsedHeartbeat, PlatformComponent, PluginApi, PluginInstallRecord, RefreshPlatformContentOptions, RemoveManagedSectionOptions, ResolvedCliConfig, ResolvedValue, RetryOptions, RunOptions, SectionId, SeedContentOptions, ServiceAccountFileConfig, ServiceManager, ServiceManagerOptions, ServiceState, SlackWorkspaceOptions, StatusHandler, StatusHandlerResult, StatusResponse, ToolDescriptor, ToolRegistrationOptions, ToolResult, UpdateManagedSectionOptions, VersionStamp, WorkspaceConfig, WorkspaceOptions, WriteComponentVersionOptions };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import cp, { execSync } from 'node:child_process';
|
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { packageDirectorySync } from 'package-directory';
|
|
13
|
+
import Handlebars from 'handlebars';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Comment markers for managed content blocks.
|
|
@@ -183,14 +184,14 @@ const PLATFORM_COMPONENTS = [
|
|
|
183
184
|
* Core library version, inlined at build time.
|
|
184
185
|
*
|
|
185
186
|
* @remarks
|
|
186
|
-
* The `0.5.
|
|
187
|
+
* The `0.5.8` placeholder is replaced by
|
|
187
188
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
188
189
|
* from `package.json`. This ensures the correct version survives
|
|
189
190
|
* when consumers bundle core into their own dist (where runtime
|
|
190
191
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
191
192
|
*/
|
|
192
193
|
/** The core library version from package.json (inlined at build time). */
|
|
193
|
-
const CORE_VERSION = '0.5.
|
|
194
|
+
const CORE_VERSION = '0.5.8';
|
|
194
195
|
|
|
195
196
|
/**
|
|
196
197
|
* Workspace and config root initialization.
|
|
@@ -213,6 +214,7 @@ function init(options) {
|
|
|
213
214
|
workspacePath: options.workspacePath,
|
|
214
215
|
configRoot: options.configRoot,
|
|
215
216
|
coreConfigDir: join(options.configRoot, CORE_CONFIG_DIR),
|
|
217
|
+
componentConfigPaths: new Map(),
|
|
216
218
|
};
|
|
217
219
|
}
|
|
218
220
|
/**
|
|
@@ -267,6 +269,36 @@ function getComponentConfigDir(componentName) {
|
|
|
267
269
|
throw new Error('jeeves-core: init() must be called first');
|
|
268
270
|
return join(state.configRoot, `${COMPONENT_CONFIG_PREFIX}${componentName}`);
|
|
269
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Register the actual config file path for a component.
|
|
274
|
+
*
|
|
275
|
+
* @remarks
|
|
276
|
+
* Call this after resolving the `--config` CLI argument so that
|
|
277
|
+
* `getComponentConfigPath` returns the real file location instead of
|
|
278
|
+
* the default derived from `configRoot`.
|
|
279
|
+
*
|
|
280
|
+
* @param componentName - The component name (e.g., 'watcher', 'runner').
|
|
281
|
+
* @param absolutePath - Absolute path to the component's config file.
|
|
282
|
+
* @throws Error if `init()` has not been called.
|
|
283
|
+
*/
|
|
284
|
+
function registerComponentConfigPath(componentName, absolutePath) {
|
|
285
|
+
if (!state)
|
|
286
|
+
throw new Error('jeeves-core: init() must be called first');
|
|
287
|
+
state.componentConfigPaths.set(componentName, absolutePath);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get the registered config file path for a component, or `undefined`
|
|
291
|
+
* if no override has been registered.
|
|
292
|
+
*
|
|
293
|
+
* @param componentName - The component name.
|
|
294
|
+
* @returns The registered absolute path, or `undefined`.
|
|
295
|
+
* @throws Error if `init()` has not been called.
|
|
296
|
+
*/
|
|
297
|
+
function getComponentConfigPath(componentName) {
|
|
298
|
+
if (!state)
|
|
299
|
+
throw new Error('jeeves-core: init() must be called first');
|
|
300
|
+
return state.componentConfigPaths.get(componentName);
|
|
301
|
+
}
|
|
270
302
|
/**
|
|
271
303
|
* Reset initialization state. Used for testing only.
|
|
272
304
|
*/
|
|
@@ -410,6 +442,32 @@ async function withWorkspaceLock(workspacePath, fn) {
|
|
|
410
442
|
function getErrorMessage(err) {
|
|
411
443
|
return err instanceof Error ? err.message : String(err);
|
|
412
444
|
}
|
|
445
|
+
/** Error codes / names that indicate transient network failures. */
|
|
446
|
+
const TRANSIENT_CODES = new Set([
|
|
447
|
+
'ECONNRESET',
|
|
448
|
+
'ETIMEDOUT',
|
|
449
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
450
|
+
'AbortError',
|
|
451
|
+
]);
|
|
452
|
+
/**
|
|
453
|
+
* Classify whether an error is a transient network failure.
|
|
454
|
+
*
|
|
455
|
+
* @param err - The caught value.
|
|
456
|
+
* @returns `true` for ECONNRESET, ETIMEDOUT, UND_ERR_CONNECT_TIMEOUT,
|
|
457
|
+
* AbortError, and timeout-related fetch errors.
|
|
458
|
+
*/
|
|
459
|
+
function isTransientError(err) {
|
|
460
|
+
let current = err;
|
|
461
|
+
while (current instanceof Error) {
|
|
462
|
+
if (TRANSIENT_CODES.has(current.name))
|
|
463
|
+
return true;
|
|
464
|
+
const code = current.code;
|
|
465
|
+
if (typeof code === 'string' && TRANSIENT_CODES.has(code))
|
|
466
|
+
return true;
|
|
467
|
+
current = current.cause;
|
|
468
|
+
}
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
413
471
|
|
|
414
472
|
/**
|
|
415
473
|
* Factory for a framework-agnostic config apply HTTP handler.
|
|
@@ -484,9 +542,9 @@ function readConfigFile(filePath) {
|
|
|
484
542
|
function createConfigApplyHandler(descriptor) {
|
|
485
543
|
return async (request) => {
|
|
486
544
|
const { patch, replace } = request;
|
|
487
|
-
//
|
|
488
|
-
const
|
|
489
|
-
|
|
545
|
+
// Use registered config path if available, otherwise derive from configRoot
|
|
546
|
+
const configPath = getComponentConfigPath(descriptor.name) ??
|
|
547
|
+
join(getComponentConfigDir(descriptor.name), descriptor.configFileName);
|
|
490
548
|
// Read existing config
|
|
491
549
|
const existing = readConfigFile(configPath);
|
|
492
550
|
// Merge or replace
|
|
@@ -667,6 +725,11 @@ const workspaceCoreConfigSchema = z
|
|
|
667
725
|
configRoot: z.string().optional().describe('Platform config root path'),
|
|
668
726
|
/** OpenClaw gateway URL. */
|
|
669
727
|
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
728
|
+
/** Dev repo paths keyed by component name. */
|
|
729
|
+
devRepos: z
|
|
730
|
+
.record(z.string(), z.string())
|
|
731
|
+
.optional()
|
|
732
|
+
.describe('Dev repo paths by component name'),
|
|
670
733
|
})
|
|
671
734
|
.partial();
|
|
672
735
|
/** Memory shared config section. */
|
|
@@ -681,13 +744,6 @@ const workspaceMemoryConfigSchema = z
|
|
|
681
744
|
.max(1)
|
|
682
745
|
.optional()
|
|
683
746
|
.describe('Memory warning threshold'),
|
|
684
|
-
/** Staleness threshold in days. */
|
|
685
|
-
staleDays: z
|
|
686
|
-
.number()
|
|
687
|
-
.int()
|
|
688
|
-
.positive()
|
|
689
|
-
.optional()
|
|
690
|
-
.describe('Memory staleness threshold in days'),
|
|
691
747
|
})
|
|
692
748
|
.partial();
|
|
693
749
|
/** Workspace config Zod schema. */
|
|
@@ -715,7 +771,6 @@ const WORKSPACE_CONFIG_DEFAULTS = {
|
|
|
715
771
|
memory: {
|
|
716
772
|
budget: 20_000,
|
|
717
773
|
warningThreshold: 0.8,
|
|
718
|
-
staleDays: 30,
|
|
719
774
|
},
|
|
720
775
|
};
|
|
721
776
|
/**
|
|
@@ -783,6 +838,11 @@ function generateWorkspaceJsonSchema() {
|
|
|
783
838
|
type: 'string',
|
|
784
839
|
default: WORKSPACE_CONFIG_DEFAULTS.core.gatewayUrl,
|
|
785
840
|
},
|
|
841
|
+
devRepos: {
|
|
842
|
+
type: 'object',
|
|
843
|
+
additionalProperties: { type: 'string' },
|
|
844
|
+
description: 'Dev repo paths by component name (e.g. { "core": "D:\\\\repos\\\\jeeves" })',
|
|
845
|
+
},
|
|
786
846
|
},
|
|
787
847
|
},
|
|
788
848
|
memory: {
|
|
@@ -799,11 +859,6 @@ function generateWorkspaceJsonSchema() {
|
|
|
799
859
|
maximum: 1,
|
|
800
860
|
default: WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold,
|
|
801
861
|
},
|
|
802
|
-
staleDays: {
|
|
803
|
-
type: 'integer',
|
|
804
|
-
minimum: 1,
|
|
805
|
-
default: WORKSPACE_CONFIG_DEFAULTS.memory.staleDays,
|
|
806
|
-
},
|
|
807
862
|
},
|
|
808
863
|
},
|
|
809
864
|
},
|
|
@@ -847,7 +902,6 @@ function resolveCliConfig(opts) {
|
|
|
847
902
|
memory: {
|
|
848
903
|
budget: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_BUDGET'), fileConfig?.memory?.budget, WORKSPACE_CONFIG_DEFAULTS.memory.budget),
|
|
849
904
|
warningThreshold: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_WARNING_THRESHOLD'), fileConfig?.memory?.warningThreshold, WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold),
|
|
850
|
-
staleDays: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_STALE_DAYS'), fileConfig?.memory?.staleDays, WORKSPACE_CONFIG_DEFAULTS.memory.staleDays),
|
|
851
905
|
},
|
|
852
906
|
};
|
|
853
907
|
}
|
|
@@ -1517,7 +1571,7 @@ Managed blocks are stationary after initial insertion. Cleanup detection uses Ja
|
|
|
1517
1571
|
|
|
1518
1572
|
\`jeeves.config.json\` at workspace root provides shared defaults:
|
|
1519
1573
|
- Precedence: CLI flags → env vars → file → defaults
|
|
1520
|
-
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold
|
|
1574
|
+
- Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl, devRepos) and \`memory.*\` (budget, warningThreshold)
|
|
1521
1575
|
- Inspect with \`jeeves config [jsonpath]\`
|
|
1522
1576
|
|
|
1523
1577
|
## HEARTBEAT Protocol
|
|
@@ -3157,11 +3211,6 @@ function createServiceCli(descriptor) {
|
|
|
3157
3211
|
});
|
|
3158
3212
|
// Apply custom CLI commands if provided
|
|
3159
3213
|
if (descriptor.customCliCommands) {
|
|
3160
|
-
// Cast required: @commander-js/extra-typings Command has generic type
|
|
3161
|
-
// parameters that don't align with the descriptor's base Command type.
|
|
3162
|
-
// The descriptor can't know the parent Command's exact generic parameters
|
|
3163
|
-
// at definition time. The cast is safe — customCliCommands only adds
|
|
3164
|
-
// subcommands and doesn't depend on the parent's generic state.
|
|
3165
3214
|
descriptor.customCliCommands(program);
|
|
3166
3215
|
}
|
|
3167
3216
|
return program;
|
|
@@ -3446,7 +3495,7 @@ Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not
|
|
|
3446
3495
|
## Platform Surface Conventions
|
|
3447
3496
|
|
|
3448
3497
|
**Slack:**
|
|
3449
|
-
-
|
|
3498
|
+
- Never initiate a threaded reply. Only reply within a thread started by a human.
|
|
3450
3499
|
- Use \`<#C…>\` for channel references
|
|
3451
3500
|
|
|
3452
3501
|
**Table formatting:** On channels that do not support Markdown tables (Slack, Discord, WhatsApp, IRC), use code-block tables with aligned columns. Markdown tables are only safe in contexts that render them (GitHub, jeeves-server, files).
|
|
@@ -3652,8 +3701,8 @@ Never manually edit \`~/.openclaw/extensions/\`. Always use the CLI commands abo
|
|
|
3652
3701
|
|
|
3653
3702
|
### Reference Templates
|
|
3654
3703
|
|
|
3655
|
-
|
|
3656
|
-
Reference templates are available at \`
|
|
3704
|
+
{{#if templatePath}}
|
|
3705
|
+
Reference templates are available at \`{{templatePath}}\`:
|
|
3657
3706
|
|
|
3658
3707
|
| Template | Purpose |
|
|
3659
3708
|
|----------|---------|
|
|
@@ -3661,9 +3710,39 @@ Reference templates are available at \`__TEMPLATE_PATH__\`:
|
|
|
3661
3710
|
| \`spec-to-code-guide.md\` | The spec-to-code development practice — 7-stage iterative process, convergence loops, release gates |
|
|
3662
3711
|
|
|
3663
3712
|
Read these templates when creating new specs, onboarding to new projects, or when asked about the development process.
|
|
3664
|
-
|
|
3713
|
+
{{else}}
|
|
3665
3714
|
> Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
|
|
3666
|
-
|
|
3715
|
+
{{/if}}
|
|
3716
|
+
|
|
3717
|
+
### Post-Upgrade Maintenance
|
|
3718
|
+
|
|
3719
|
+
After updating OpenClaw (\`npm install -g openclaw@latest\` or equivalent), reinstall all Jeeves component plugins to repair install state:
|
|
3720
|
+
|
|
3721
|
+
\`\`\`bash
|
|
3722
|
+
npx @karmaniverous/jeeves install
|
|
3723
|
+
npx @karmaniverous/jeeves-runner-openclaw install
|
|
3724
|
+
npx @karmaniverous/jeeves-watcher-openclaw install
|
|
3725
|
+
npx @karmaniverous/jeeves-server-openclaw install
|
|
3726
|
+
npx @karmaniverous/jeeves-meta-openclaw install
|
|
3727
|
+
\`\`\`
|
|
3728
|
+
|
|
3729
|
+
Then restart the gateway. Plugin installers copy dist files and patch config; reinstalling after an OpenClaw update ensures the extensions directory stays consistent.
|
|
3730
|
+
|
|
3731
|
+
### Source Code Preference
|
|
3732
|
+
|
|
3733
|
+
{{#if devRepos}}
|
|
3734
|
+
When investigating, debugging, or analyzing Jeeves components, always read TypeScript source from dev repos — never compiled \`dist/\` from the global npm install. Dev repos:
|
|
3735
|
+
|
|
3736
|
+
| Component | Dev Repo |
|
|
3737
|
+
|-----------|----------|
|
|
3738
|
+
{{#each devRepos}}
|
|
3739
|
+
| {{@key}} | \`{{this}}\` |
|
|
3740
|
+
{{/each}}
|
|
3741
|
+
|
|
3742
|
+
Built code is minified, harder to reason about, and wastes context. Always \`git pull\` before analysis.
|
|
3743
|
+
{{else}}
|
|
3744
|
+
> Dev repo paths not configured. Add \`core.devRepos\` to \`jeeves.config.json\` to enable source code preference guidance.
|
|
3745
|
+
{{/if}}
|
|
3667
3746
|
`;
|
|
3668
3747
|
|
|
3669
3748
|
/**
|
|
@@ -3675,6 +3754,10 @@ Read these templates when creating new specs, onboarding to new projects, or whe
|
|
|
3675
3754
|
* Platform template with live data, and writes managed sections using
|
|
3676
3755
|
* `updateManagedSection`.
|
|
3677
3756
|
*/
|
|
3757
|
+
/** Compiled Handlebars template for the Platform section (cached at module level). */
|
|
3758
|
+
const compiledPlatformTemplate = Handlebars.compile(toolsPlatformTemplate, {
|
|
3759
|
+
noEscape: true,
|
|
3760
|
+
});
|
|
3678
3761
|
/**
|
|
3679
3762
|
* Resolve the package's content directory for template file copying.
|
|
3680
3763
|
*
|
|
@@ -3718,23 +3801,13 @@ function copyTemplates(coreConfigDir) {
|
|
|
3718
3801
|
cpSync(sourceDir, destDir, { recursive: true });
|
|
3719
3802
|
}
|
|
3720
3803
|
/**
|
|
3721
|
-
* Render the Platform template using
|
|
3804
|
+
* Render the Platform template using Handlebars.
|
|
3722
3805
|
*
|
|
3723
|
-
* @param
|
|
3806
|
+
* @param context - Template context with optional templatePath and devRepos.
|
|
3724
3807
|
* @returns Rendered platform content string.
|
|
3725
3808
|
*/
|
|
3726
|
-
function renderPlatformTemplate(
|
|
3727
|
-
|
|
3728
|
-
let content = toolsPlatformTemplate;
|
|
3729
|
-
// Handle <!-- IF_TEMPLATES --> ... <!-- ELSE_TEMPLATES --> ... <!-- ENDIF_TEMPLATES --> block
|
|
3730
|
-
const ifRegex = /<!-- IF_TEMPLATES -->([\s\S]*?)<!-- ELSE_TEMPLATES -->([\s\S]*?)<!-- ENDIF_TEMPLATES -->/;
|
|
3731
|
-
const match = ifRegex.exec(content);
|
|
3732
|
-
if (match) {
|
|
3733
|
-
content = content.replace(match[0], templatesAvailable ? match[1] : match[2]);
|
|
3734
|
-
}
|
|
3735
|
-
// Replace __TEMPLATE_PATH__ with the actual path
|
|
3736
|
-
content = content.replace(/__TEMPLATE_PATH__/g, templatePath);
|
|
3737
|
-
return content;
|
|
3809
|
+
function renderPlatformTemplate(context) {
|
|
3810
|
+
return compiledPlatformTemplate(context);
|
|
3738
3811
|
}
|
|
3739
3812
|
/**
|
|
3740
3813
|
* Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
|
|
@@ -3742,7 +3815,7 @@ function renderPlatformTemplate(templatePath) {
|
|
|
3742
3815
|
* @param options - Configuration for the refresh cycle.
|
|
3743
3816
|
*/
|
|
3744
3817
|
async function refreshPlatformContent(options) {
|
|
3745
|
-
const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, } = options;
|
|
3818
|
+
const { coreVersion, componentName, componentVersion, servicePackage, pluginPackage, stalenessThresholdMs, workspaceConfig, } = options;
|
|
3746
3819
|
const workspacePath = getWorkspacePath();
|
|
3747
3820
|
const coreConfigDir = getCoreConfigDir();
|
|
3748
3821
|
// 1. Write calling component's version entry
|
|
@@ -3756,7 +3829,11 @@ async function refreshPlatformContent(options) {
|
|
|
3756
3829
|
}
|
|
3757
3830
|
// 2. Render Platform template
|
|
3758
3831
|
const templatePath = join(coreConfigDir, TEMPLATES_DIR);
|
|
3759
|
-
const
|
|
3832
|
+
const wsConfig = workspaceConfig ?? loadWorkspaceConfig(workspacePath);
|
|
3833
|
+
const platformContent = renderPlatformTemplate({
|
|
3834
|
+
templatePath: existsSync(templatePath) ? templatePath : undefined,
|
|
3835
|
+
devRepos: wsConfig?.core?.devRepos,
|
|
3836
|
+
});
|
|
3760
3837
|
// 3. Write TOOLS.md Platform section
|
|
3761
3838
|
const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
|
|
3762
3839
|
await updateManagedSection(toolsPath, platformContent, {
|
|
@@ -3884,46 +3961,21 @@ function scanAndEscalateCleanup(targets, gatewayUrl, pendingCleanups) {
|
|
|
3884
3961
|
}
|
|
3885
3962
|
|
|
3886
3963
|
/**
|
|
3887
|
-
* Memory budget accounting
|
|
3964
|
+
* Memory budget accounting for MEMORY.md.
|
|
3888
3965
|
*
|
|
3889
3966
|
* @remarks
|
|
3890
|
-
*
|
|
3891
|
-
*
|
|
3892
|
-
*
|
|
3893
|
-
* human- or agent-mediated (Decision 42).
|
|
3967
|
+
* Reports character count against a configured budget and warning threshold
|
|
3968
|
+
* state. Does not auto-delete: review remains human- or agent-mediated
|
|
3969
|
+
* (Decision 42). Staleness detection removed in v0.5.9 (Decision 45).
|
|
3894
3970
|
*/
|
|
3895
|
-
/** ISO date pattern: YYYY-MM-DD. */
|
|
3896
|
-
const ISO_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/g;
|
|
3897
|
-
/** H2 heading pattern used to split sections. */
|
|
3898
|
-
const H2_RE = /^## /m;
|
|
3899
3971
|
/**
|
|
3900
|
-
*
|
|
3901
|
-
*
|
|
3902
|
-
* @param text - Text to scan for dates.
|
|
3903
|
-
* @returns The most recent date found, or undefined.
|
|
3904
|
-
*/
|
|
3905
|
-
function extractMostRecentDate(text) {
|
|
3906
|
-
const matches = text.match(ISO_DATE_RE);
|
|
3907
|
-
if (!matches)
|
|
3908
|
-
return undefined;
|
|
3909
|
-
let latest;
|
|
3910
|
-
for (const match of matches) {
|
|
3911
|
-
const d = new Date(match + 'T00:00:00Z');
|
|
3912
|
-
if (!Number.isNaN(d.getTime())) {
|
|
3913
|
-
if (!latest || d > latest)
|
|
3914
|
-
latest = d;
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
return latest;
|
|
3918
|
-
}
|
|
3919
|
-
/**
|
|
3920
|
-
* Analyze MEMORY.md for budget and staleness.
|
|
3972
|
+
* Analyze MEMORY.md for budget health.
|
|
3921
3973
|
*
|
|
3922
3974
|
* @param options - Analysis configuration.
|
|
3923
3975
|
* @returns Memory hygiene result.
|
|
3924
3976
|
*/
|
|
3925
3977
|
function analyzeMemory(options) {
|
|
3926
|
-
const { workspacePath, budget, warningThreshold
|
|
3978
|
+
const { workspacePath, budget, warningThreshold } = options;
|
|
3927
3979
|
const memoryPath = join(workspacePath, WORKSPACE_FILES.memory);
|
|
3928
3980
|
if (!existsSync(memoryPath)) {
|
|
3929
3981
|
return {
|
|
@@ -3933,8 +3985,6 @@ function analyzeMemory(options) {
|
|
|
3933
3985
|
usage: 0,
|
|
3934
3986
|
warning: false,
|
|
3935
3987
|
overBudget: false,
|
|
3936
|
-
staleCandidates: 0,
|
|
3937
|
-
staleSectionNames: [],
|
|
3938
3988
|
};
|
|
3939
3989
|
}
|
|
3940
3990
|
const content = readFileSync(memoryPath, 'utf-8');
|
|
@@ -3942,21 +3992,6 @@ function analyzeMemory(options) {
|
|
|
3942
3992
|
const usage = budget > 0 ? charCount / budget : charCount > 0 ? Infinity : 0;
|
|
3943
3993
|
const warning = usage >= warningThreshold;
|
|
3944
3994
|
const overBudget = usage > 1;
|
|
3945
|
-
// Split into H2 sections and scan for staleness
|
|
3946
|
-
const sections = content.split(H2_RE).slice(1); // skip content before first H2
|
|
3947
|
-
const now = Date.now();
|
|
3948
|
-
const thresholdMs = staleDays * 24 * 60 * 60 * 1000;
|
|
3949
|
-
const staleSectionNames = [];
|
|
3950
|
-
for (const section of sections) {
|
|
3951
|
-
const sectionName = section.split('\n')[0]?.trim() ?? '';
|
|
3952
|
-
const recentDate = extractMostRecentDate(section);
|
|
3953
|
-
// Sections without dates are evergreen — never flagged (Decision 47)
|
|
3954
|
-
if (!recentDate)
|
|
3955
|
-
continue;
|
|
3956
|
-
if (now - recentDate.getTime() > thresholdMs) {
|
|
3957
|
-
staleSectionNames.push(sectionName);
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
3995
|
return {
|
|
3961
3996
|
exists: true,
|
|
3962
3997
|
charCount,
|
|
@@ -3964,8 +3999,6 @@ function analyzeMemory(options) {
|
|
|
3964
3999
|
usage,
|
|
3965
4000
|
warning,
|
|
3966
4001
|
overBudget,
|
|
3967
|
-
staleCandidates: staleSectionNames.length,
|
|
3968
|
-
staleSectionNames,
|
|
3969
4002
|
};
|
|
3970
4003
|
}
|
|
3971
4004
|
|
|
@@ -3992,20 +4025,14 @@ function checkMemoryHealth(options) {
|
|
|
3992
4025
|
const result = analyzeMemory(options);
|
|
3993
4026
|
if (!result.exists)
|
|
3994
4027
|
return undefined;
|
|
3995
|
-
if (!result.warning
|
|
4028
|
+
if (!result.warning)
|
|
3996
4029
|
return undefined;
|
|
3997
|
-
const
|
|
3998
|
-
|
|
3999
|
-
const pct = Math.round(result.usage * 100);
|
|
4000
|
-
lines.push(`- Budget: ${result.charCount.toLocaleString()} / ${result.budget.toLocaleString()} chars (${String(pct)}%).${result.overBudget ? ' **Over budget.**' : ' Consider reviewing.'}`);
|
|
4001
|
-
}
|
|
4002
|
-
if (result.staleCandidates > 0) {
|
|
4003
|
-
lines.push(`- ${String(result.staleCandidates)} stale section${result.staleCandidates === 1 ? '' : 's'}: ${result.staleSectionNames.join(', ')}`);
|
|
4004
|
-
}
|
|
4030
|
+
const pct = Math.round(result.usage * 100);
|
|
4031
|
+
const content = `- Budget: ${result.charCount.toLocaleString()} / ${result.budget.toLocaleString()} chars (${String(pct)}%).${result.overBudget ? ' **Over budget.**' : ' Consider reviewing.'}`;
|
|
4005
4032
|
return {
|
|
4006
4033
|
name: MEMORY_HEARTBEAT_NAME,
|
|
4007
4034
|
declined: false,
|
|
4008
|
-
content
|
|
4035
|
+
content,
|
|
4009
4036
|
};
|
|
4010
4037
|
}
|
|
4011
4038
|
|
|
@@ -4245,9 +4272,8 @@ function getServiceUrl(serviceName, consumerName) {
|
|
|
4245
4272
|
if (coreUrl)
|
|
4246
4273
|
return coreUrl;
|
|
4247
4274
|
// 3. Fall back to port constants
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
return `http://127.0.0.1:${String(port)}`;
|
|
4275
|
+
if (serviceName in DEFAULT_PORTS) {
|
|
4276
|
+
return `http://127.0.0.1:${String(DEFAULT_PORTS[serviceName])}`;
|
|
4251
4277
|
}
|
|
4252
4278
|
throw new Error(`jeeves-core: unknown service "${serviceName}" and no config found`);
|
|
4253
4279
|
}
|
|
@@ -4571,6 +4597,7 @@ function readFileOrEmpty(filePath) {
|
|
|
4571
4597
|
*/
|
|
4572
4598
|
async function runHeartbeatCycle(options) {
|
|
4573
4599
|
const { workspacePath, coreConfigDir, configRoot } = options;
|
|
4600
|
+
const wsConfig = options.workspaceConfig ?? loadWorkspaceConfig(workspacePath);
|
|
4574
4601
|
const heartbeatPath = join(workspacePath, WORKSPACE_FILES.heartbeat);
|
|
4575
4602
|
try {
|
|
4576
4603
|
const existingContent = readFileOrEmpty(heartbeatPath);
|
|
@@ -4583,14 +4610,11 @@ async function runHeartbeatCycle(options) {
|
|
|
4583
4610
|
});
|
|
4584
4611
|
// Memory hygiene check (Decision 49)
|
|
4585
4612
|
if (!declinedNames.has(MEMORY_HEARTBEAT_NAME)) {
|
|
4586
|
-
const wsConfig = loadWorkspaceConfig(workspacePath);
|
|
4587
4613
|
const memoryEntry = checkMemoryHealth({
|
|
4588
4614
|
workspacePath,
|
|
4589
4615
|
budget: wsConfig?.memory?.budget ?? WORKSPACE_CONFIG_DEFAULTS.memory.budget,
|
|
4590
4616
|
warningThreshold: wsConfig?.memory?.warningThreshold ??
|
|
4591
4617
|
WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold,
|
|
4592
|
-
staleDays: wsConfig?.memory?.staleDays ??
|
|
4593
|
-
WORKSPACE_CONFIG_DEFAULTS.memory.staleDays,
|
|
4594
4618
|
});
|
|
4595
4619
|
if (memoryEntry)
|
|
4596
4620
|
entries.push(memoryEntry);
|
|
@@ -4644,6 +4668,7 @@ class ComponentWriter {
|
|
|
4644
4668
|
gatewayUrl;
|
|
4645
4669
|
pendingCleanups = new Set();
|
|
4646
4670
|
cyclePromise;
|
|
4671
|
+
stopped = false;
|
|
4647
4672
|
/** @internal */
|
|
4648
4673
|
constructor(component, options) {
|
|
4649
4674
|
this.component = component;
|
|
@@ -4669,6 +4694,7 @@ class ComponentWriter {
|
|
|
4669
4694
|
* contention on startup.
|
|
4670
4695
|
*/
|
|
4671
4696
|
start() {
|
|
4697
|
+
this.stopped = false;
|
|
4672
4698
|
if (this.isRunning)
|
|
4673
4699
|
return;
|
|
4674
4700
|
// Random jitter up to one full interval to spread initial writes
|
|
@@ -4681,6 +4707,7 @@ class ComponentWriter {
|
|
|
4681
4707
|
}
|
|
4682
4708
|
/** Stop the writer timer. */
|
|
4683
4709
|
stop() {
|
|
4710
|
+
this.stopped = true;
|
|
4684
4711
|
if (this.jitterTimeout) {
|
|
4685
4712
|
clearTimeout(this.jitterTimeout);
|
|
4686
4713
|
this.jitterTimeout = undefined;
|
|
@@ -4694,7 +4721,7 @@ class ComponentWriter {
|
|
|
4694
4721
|
this.timer = setTimeout(() => {
|
|
4695
4722
|
this.timer = undefined;
|
|
4696
4723
|
void this.cycle().finally(() => {
|
|
4697
|
-
if (this.
|
|
4724
|
+
if (!this.stopped)
|
|
4698
4725
|
this.scheduleNextCycle(intervalMs, intervalMs);
|
|
4699
4726
|
});
|
|
4700
4727
|
}, delayMs);
|
|
@@ -4729,15 +4756,18 @@ class ComponentWriter {
|
|
|
4729
4756
|
markers: TOOLS_MARKERS,
|
|
4730
4757
|
coreVersion: CORE_VERSION,
|
|
4731
4758
|
});
|
|
4732
|
-
// 2.
|
|
4759
|
+
// 2. Load workspace config once for the entire cycle
|
|
4760
|
+
const workspaceConfig = loadWorkspaceConfig(workspacePath);
|
|
4761
|
+
// 3. Platform content maintenance
|
|
4733
4762
|
await refreshPlatformContent({
|
|
4734
4763
|
coreVersion: CORE_VERSION,
|
|
4735
4764
|
componentName: this.component.name,
|
|
4736
4765
|
componentVersion: this.component.version,
|
|
4737
4766
|
servicePackage: this.component.servicePackage,
|
|
4738
4767
|
pluginPackage: this.component.pluginPackage,
|
|
4768
|
+
workspaceConfig,
|
|
4739
4769
|
});
|
|
4740
|
-
//
|
|
4770
|
+
// 4. Cleanup escalation
|
|
4741
4771
|
if (this.gatewayUrl) {
|
|
4742
4772
|
scanAndEscalateCleanup([
|
|
4743
4773
|
{ filePath: toolsPath, markerIdentity: 'TOOLS' },
|
|
@@ -4751,11 +4781,12 @@ class ComponentWriter {
|
|
|
4751
4781
|
},
|
|
4752
4782
|
], this.gatewayUrl, this.pendingCleanups);
|
|
4753
4783
|
}
|
|
4754
|
-
//
|
|
4784
|
+
// 5. HEARTBEAT orchestration
|
|
4755
4785
|
await runHeartbeatCycle({
|
|
4756
4786
|
workspacePath,
|
|
4757
4787
|
coreConfigDir: getCoreConfigDir(),
|
|
4758
4788
|
configRoot: getConfigRoot(),
|
|
4789
|
+
workspaceConfig,
|
|
4759
4790
|
});
|
|
4760
4791
|
});
|
|
4761
4792
|
}
|
|
@@ -4803,7 +4834,12 @@ class ComponentWriter {
|
|
|
4803
4834
|
*/
|
|
4804
4835
|
function createAsyncContentCache(options) {
|
|
4805
4836
|
const { fetch: fetchContent, placeholder = '> Initializing...', onError = (err) => {
|
|
4806
|
-
|
|
4837
|
+
if (isTransientError(err)) {
|
|
4838
|
+
console.warn(`[jeeves] cache refresh: transient error (${getErrorMessage(err)})`);
|
|
4839
|
+
}
|
|
4840
|
+
else {
|
|
4841
|
+
console.warn('[jeeves] cache refresh failed:', err);
|
|
4842
|
+
}
|
|
4807
4843
|
}, } = options;
|
|
4808
4844
|
let cached = placeholder;
|
|
4809
4845
|
let refreshing = false;
|
|
@@ -5323,4 +5359,4 @@ async function getChannelWorkspace(channelId, token, options) {
|
|
|
5323
5359
|
return teamId;
|
|
5324
5360
|
}
|
|
5325
5361
|
|
|
5326
|
-
export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir,
|
|
5362
|
+
export { AGENTS_MARKERS, CLEANUP_FLAG, COMPONENT_CONFIG_PREFIX, COMPONENT_VERSIONS_FILE, CONFIG_FILE, CORE_CONFIG_DIR, CORE_VERSION, ComponentWriter, DEFAULT_BIND_ADDRESS, DEFAULT_CORE_VERSION, DEFAULT_PORTS, HEARTBEAT_HEADING, JEEVES_SKILL_DIR, MEMORY_HEARTBEAT_NAME, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SKILLS_DIR, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_CONFIG_DEFAULTS, WORKSPACE_CONFIG_FILE, WORKSPACE_FILES, analyzeMemory, appendJsonl, atomicWrite, buildEffectiveConfig, buildHeartbeatSection, checkMemoryHealth, checkNodeVersion, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getComponentConfigPath, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getErrorMessage, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, isTransientError, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, registerComponentConfigPath, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveConfigValue, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, seedSkill, shingles, shouldWrite, sleepAsync, sleepMs, updateManagedSection, uuid, withFileLock, workspaceConfigSchema, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
|
package/package.json
CHANGED
|
@@ -14,16 +14,17 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"commander": "^14.0.3",
|
|
17
|
+
"handlebars": "^4.7.9",
|
|
17
18
|
"jsonpath-plus": "^10.4.0",
|
|
18
19
|
"package-directory": "^8.2.0",
|
|
19
20
|
"proper-lockfile": "^4.1.2",
|
|
20
21
|
"semver": "^7.7.4",
|
|
21
|
-
"zod": "^4.
|
|
22
|
+
"zod": "^4.4.2"
|
|
22
23
|
},
|
|
23
24
|
"description": "Shared library and CLI for the Jeeves AI assistant platform.",
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@commander-js/extra-typings": "^14.0.0",
|
|
26
|
-
"@dotenvx/dotenvx": "^1.
|
|
27
|
+
"@dotenvx/dotenvx": "^1.64.0",
|
|
27
28
|
"@eslint/js": "^10.0.1",
|
|
28
29
|
"@rollup/plugin-alias": "^6.0.0",
|
|
29
30
|
"@rollup/plugin-commonjs": "^29.0.2",
|
|
@@ -32,33 +33,33 @@
|
|
|
32
33
|
"@rollup/plugin-replace": "^6.0.3",
|
|
33
34
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
34
35
|
"@types/fs-extra": "^11.0.4",
|
|
35
|
-
"@types/node": "^25.
|
|
36
|
+
"@types/node": "^25.6.0",
|
|
36
37
|
"@types/proper-lockfile": "^4.1.4",
|
|
37
38
|
"@types/semver": "^7.7.1",
|
|
38
|
-
"@vitest/coverage-v8": "^4.1.
|
|
39
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
39
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
40
|
+
"@vitest/eslint-plugin": "^1.6.16",
|
|
40
41
|
"auto-changelog": "^2.5.0",
|
|
41
42
|
"cross-env": "^10.1.0",
|
|
42
|
-
"eslint": "^10.
|
|
43
|
+
"eslint": "^10.3.0",
|
|
43
44
|
"eslint-config-prettier": "^10.1.8",
|
|
44
45
|
"eslint-plugin-prettier": "^5.5.5",
|
|
45
46
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
46
47
|
"eslint-plugin-tsdoc": "^0.5.2",
|
|
47
48
|
"fs-extra": "^11.3.4",
|
|
48
|
-
"knip": "^6.
|
|
49
|
-
"lefthook": "^2.1.
|
|
50
|
-
"prettier": "^3.8.
|
|
49
|
+
"knip": "^6.11.0",
|
|
50
|
+
"lefthook": "^2.1.6",
|
|
51
|
+
"prettier": "^3.8.3",
|
|
51
52
|
"release-it": "^19.2.4",
|
|
52
53
|
"rimraf": "^6.1.3",
|
|
53
|
-
"rollup": "^4.60.
|
|
54
|
+
"rollup": "^4.60.2",
|
|
54
55
|
"rollup-plugin-dts": "^6.4.1",
|
|
55
56
|
"tslib": "^2.8.1",
|
|
56
|
-
"typedoc": "^0.28.
|
|
57
|
+
"typedoc": "^0.28.19",
|
|
57
58
|
"typedoc-plugin-mdn-links": "^5.1.1",
|
|
58
59
|
"typedoc-plugin-replace-text": "^4.2.0",
|
|
59
60
|
"typescript": "^5.9.3",
|
|
60
|
-
"typescript-eslint": "^8.
|
|
61
|
-
"vitest": "^4.1.
|
|
61
|
+
"typescript-eslint": "^8.59.1",
|
|
62
|
+
"vitest": "^4.1.5"
|
|
62
63
|
},
|
|
63
64
|
"engines": {
|
|
64
65
|
"node": ">=22"
|
|
@@ -139,5 +140,5 @@
|
|
|
139
140
|
},
|
|
140
141
|
"type": "module",
|
|
141
142
|
"types": "dist/index.d.ts",
|
|
142
|
-
"version": "0.5.
|
|
143
|
+
"version": "0.5.9"
|
|
143
144
|
}
|