@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 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; the assistant or human reviews stale candidates and decides what to prune.
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
- - No threaded replies by default
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, staleDays)
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
- <!-- IF_TEMPLATES -->
48
- Reference templates are available at `__TEMPLATE_PATH__`:
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
- <!-- ELSE_TEMPLATES -->
56
+ {{else}}
57
57
  > Reference templates not yet installed. Run `npx @karmaniverous/jeeves install` to seed templates.
58
- <!-- ENDIF_TEMPLATES -->
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}}
@@ -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.6` placeholder is replaced by
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.6';
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
- const port = DEFAULT_PORTS[serviceName];
824
- if (port !== undefined) {
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
- - No threaded replies by default
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
- <!-- IF_TEMPLATES -->
1387
- Reference templates are available at \`__TEMPLATE_PATH__\`:
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
- <!-- ELSE_TEMPLATES -->
1392
+ {{else}}
1396
1393
  > Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
1397
- <!-- ENDIF_TEMPLATES -->
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 simple string replacement.
1920
+ * Render the Platform template using Handlebars.
1890
1921
  *
1891
- * @param templatePath - Path to the templates directory.
1922
+ * @param context - Template context with optional templatePath and devRepos.
1892
1923
  * @returns Rendered platform content string.
1893
1924
  */
1894
- function renderPlatformTemplate(templatePath) {
1895
- const templatesAvailable = existsSync(templatePath);
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 platformContent = renderPlatformTemplate(templatePath);
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, staleDays)
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 and staleness detection for MEMORY.md.
2230
+ * Memory budget accounting for MEMORY.md.
2206
2231
  *
2207
2232
  * @remarks
2208
- * Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
2209
- * Reports character count against a configured budget, warning threshold state,
2210
- * and stale section candidates. Does not auto-delete: review remains
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
- * Extract the most recent ISO date from a string.
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, staleDays } = options;
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) {
@@ -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.6` placeholder is replaced by
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.6';
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, staleDays)
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 `console.warn`.
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 and staleness detection for MEMORY.md.
1355
+ * Memory budget accounting for MEMORY.md.
1336
1356
  *
1337
1357
  * @remarks
1338
- * Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
1339
- * Reports character count against a configured budget, warning threshold state,
1340
- * and stale section candidates. Does not auto-delete: review remains
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
- * Extract the most recent ISO date from a string.
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
- 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, extractMostRecentDate, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, 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 };
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.6` placeholder is replaced by
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.6';
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
- // Derive config path
488
- const configDir = getComponentConfigDir(descriptor.name);
489
- const configPath = join(configDir, descriptor.configFileName);
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, staleDays)
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
- - No threaded replies by default
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
- <!-- IF_TEMPLATES -->
3656
- Reference templates are available at \`__TEMPLATE_PATH__\`:
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
- <!-- ELSE_TEMPLATES -->
3713
+ {{else}}
3665
3714
  > Reference templates not yet installed. Run \`npx @karmaniverous/jeeves install\` to seed templates.
3666
- <!-- ENDIF_TEMPLATES -->
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 simple string replacement.
3804
+ * Render the Platform template using Handlebars.
3722
3805
  *
3723
- * @param templatePath - Path to the templates directory.
3806
+ * @param context - Template context with optional templatePath and devRepos.
3724
3807
  * @returns Rendered platform content string.
3725
3808
  */
3726
- function renderPlatformTemplate(templatePath) {
3727
- const templatesAvailable = existsSync(templatePath);
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 platformContent = renderPlatformTemplate(templatePath);
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 and staleness detection for MEMORY.md.
3964
+ * Memory budget accounting for MEMORY.md.
3888
3965
  *
3889
3966
  * @remarks
3890
- * Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
3891
- * Reports character count against a configured budget, warning threshold state,
3892
- * and stale section candidates. Does not auto-delete: review remains
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
- * Extract the most recent ISO date from a string.
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, staleDays } = options;
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 && result.staleCandidates === 0)
4028
+ if (!result.warning)
3996
4029
  return undefined;
3997
- const lines = [];
3998
- if (result.warning) {
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: lines.join('\n'),
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
- const port = DEFAULT_PORTS[serviceName];
4249
- if (port !== undefined) {
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.isRunning)
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. Platform content maintenance
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
- // 3. Cleanup escalation
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
- // 4. HEARTBEAT orchestration
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
- console.warn('[jeeves] async content cache refresh failed:', err);
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, extractMostRecentDate, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, generateWorkspaceJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getPackageRoot, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, loadWorkspaceConfig, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, 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 };
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.3.6"
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.59.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.5.0",
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.2",
39
- "@vitest/eslint-plugin": "^1.6.13",
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.1.0",
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.1.0",
49
- "lefthook": "^2.1.4",
50
- "prettier": "^3.8.1",
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.1",
54
+ "rollup": "^4.60.2",
54
55
  "rollup-plugin-dts": "^6.4.1",
55
56
  "tslib": "^2.8.1",
56
- "typedoc": "^0.28.18",
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.57.2",
61
- "vitest": "^4.1.2"
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.7"
143
+ "version": "0.5.9"
143
144
  }