@karmaniverous/jeeves 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import fs, { writeFileSync, renameSync, unlinkSync, existsSync, readFileSync, mkdirSync, readdirSync, copyFileSync, rmSync, cpSync } from 'node:fs';
2
- import path, { join, dirname, resolve } from 'node:path';
2
+ import path, { join, dirname, resolve, basename } from 'node:path';
3
3
  import { lock } from 'proper-lockfile';
4
4
  import { JSONPath } from 'jsonpath-plus';
5
+ import { major, valid, gte, gt } from 'semver';
6
+ import { z } from 'zod';
5
7
  import * as commander from 'commander';
6
8
  import { packageDirectorySync } from 'package-directory';
7
- import { valid, gte, gt } from 'semver';
8
9
  import { homedir } from 'node:os';
9
- import { z } from 'zod';
10
10
  import cp, { execSync } from 'node:child_process';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import crypto from 'node:crypto';
@@ -69,8 +69,8 @@ const ALL_MARKERS = [
69
69
  const VERSION_STAMP_PATTERN = /<!--\s*(.+?)\s*\|\s*core:(\S+)\s*\|\s*(\S+)\s*-->/;
70
70
  /** Staleness threshold for version-stamp convergence in milliseconds. */
71
71
  const STALENESS_THRESHOLD_MS = 5 * 60 * 1000;
72
- /** Warning text prepended inside managed block when cleanup is needed. */
73
- const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content may exist below this managed section. Review everything after the END marker and remove any content that duplicates what appears above.';
72
+ /** Warning text injected inside managed block when cleanup is needed. */
73
+ const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content detected outside this managed block. Review the file and remove any content outside the BEGIN/END markers that duplicates what appears inside them.';
74
74
 
75
75
  /**
76
76
  * Directory and file path conventions for the Jeeves platform.
@@ -89,7 +89,13 @@ const WORKSPACE_FILES = {
89
89
  agents: 'AGENTS.md',
90
90
  /** HEARTBEAT.md — platform status and health alerts. */
91
91
  heartbeat: 'HEARTBEAT.md',
92
+ /** MEMORY.md — curated long-term memory. */
93
+ memory: 'MEMORY.md',
92
94
  };
95
+ /** Skill directory name within workspace. */
96
+ const SKILLS_DIR = 'skills';
97
+ /** Jeeves skill directory name. */
98
+ const JEEVES_SKILL_DIR = 'jeeves';
93
99
  /** Templates directory name within core config. */
94
100
  const TEMPLATES_DIR = 'templates';
95
101
  /** Registry cache file name. */
@@ -177,14 +183,14 @@ const PLATFORM_COMPONENTS = [
177
183
  * Core library version, inlined at build time.
178
184
  *
179
185
  * @remarks
180
- * The `0.4.6` placeholder is replaced by
186
+ * The `0.4.7` placeholder is replaced by
181
187
  * `@rollup/plugin-replace` during the build with the actual version
182
188
  * from `package.json`. This ensures the correct version survives
183
189
  * when consumers bundle core into their own dist (where runtime
184
190
  * `import.meta.url`-based resolution would find the wrong package.json).
185
191
  */
186
192
  /** The core library version from package.json (inlined at build time). */
187
- const CORE_VERSION = '0.4.6';
193
+ const CORE_VERSION = '0.4.7';
188
194
 
189
195
  /**
190
196
  * Workspace and config root initialization.
@@ -557,6 +563,257 @@ function createStatusHandler(options) {
557
563
  };
558
564
  }
559
565
 
566
+ /**
567
+ * Runtime Node.js version floor check.
568
+ *
569
+ * @module
570
+ */
571
+ /** Minimum supported Node.js major version. */
572
+ const MIN_NODE_MAJOR = 22;
573
+ /**
574
+ * Check that the running Node.js version meets the minimum requirement.
575
+ * Prints an error and exits with code 1 if the check fails.
576
+ */
577
+ function checkNodeVersion() {
578
+ const nodeMajor = major(process.versions.node);
579
+ if (nodeMajor < MIN_NODE_MAJOR) {
580
+ console.error(`Error: jeeves requires Node.js >= ${String(MIN_NODE_MAJOR)}. Current: ${process.versions.node}`);
581
+ process.exit(1);
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Workspace-level shared configuration: `jeeves.config.json`.
587
+ *
588
+ * @remarks
589
+ * Lives at the OpenClaw workspace root alongside TOOLS.md and SOUL.md.
590
+ * Provides namespaced shared defaults consumed by the root Jeeves CLI.
591
+ * Resolution precedence: CLI flags → env vars → jeeves.config.json → defaults.
592
+ *
593
+ * This does not replace component-owned config schemas (Decision 41).
594
+ */
595
+ /** Workspace config file name. */
596
+ const WORKSPACE_CONFIG_FILE = 'jeeves.config.json';
597
+ /** Core shared config section. */
598
+ const workspaceCoreConfigSchema = z
599
+ .object({
600
+ /** Workspace root path. */
601
+ workspace: z.string().optional().describe('Workspace root path'),
602
+ /** Platform config root path. */
603
+ configRoot: z.string().optional().describe('Platform config root path'),
604
+ /** OpenClaw gateway URL. */
605
+ gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
606
+ })
607
+ .partial();
608
+ /** Memory shared config section. */
609
+ const workspaceMemoryConfigSchema = z
610
+ .object({
611
+ /** MEMORY.md character budget. */
612
+ budget: z.number().int().positive().optional().describe('Memory budget'),
613
+ /** Warning threshold as a fraction of budget. */
614
+ warningThreshold: z
615
+ .number()
616
+ .min(0)
617
+ .max(1)
618
+ .optional()
619
+ .describe('Memory warning threshold'),
620
+ /** Staleness threshold in days. */
621
+ staleDays: z
622
+ .number()
623
+ .int()
624
+ .positive()
625
+ .optional()
626
+ .describe('Memory staleness threshold in days'),
627
+ })
628
+ .partial();
629
+ /** Workspace config Zod schema. */
630
+ const workspaceConfigSchema = z.object({
631
+ /** JSON Schema pointer for IDE autocomplete. */
632
+ $schema: z.string().optional().describe('JSON Schema pointer'),
633
+ /** Core shared defaults. */
634
+ core: workspaceCoreConfigSchema.optional(),
635
+ /** Memory hygiene shared defaults. */
636
+ memory: workspaceMemoryConfigSchema.optional(),
637
+ });
638
+ /** Built-in workspace config defaults. */
639
+ const WORKSPACE_CONFIG_DEFAULTS = {
640
+ core: {
641
+ workspace: '.',
642
+ configRoot: './config',
643
+ gatewayUrl: 'http://127.0.0.1:3000',
644
+ },
645
+ memory: {
646
+ budget: 20_000,
647
+ warningThreshold: 0.8,
648
+ staleDays: 30,
649
+ },
650
+ };
651
+ /**
652
+ * Load workspace config from `jeeves.config.json` at a given path.
653
+ *
654
+ * @param workspacePath - Workspace root directory.
655
+ * @returns Parsed config or undefined if missing or invalid.
656
+ */
657
+ function loadWorkspaceConfig(workspacePath) {
658
+ const configPath = join(workspacePath, WORKSPACE_CONFIG_FILE);
659
+ if (!existsSync(configPath))
660
+ return undefined;
661
+ try {
662
+ const raw = readFileSync(configPath, 'utf-8');
663
+ const parsed = JSON.parse(raw);
664
+ return workspaceConfigSchema.parse(parsed);
665
+ }
666
+ catch (err) {
667
+ const msg = err instanceof Error ? err.message : String(err);
668
+ console.warn(`jeeves-core: failed to load ${configPath}: ${msg}`);
669
+ return undefined;
670
+ }
671
+ }
672
+ /**
673
+ * Resolve a config value with four-tier precedence.
674
+ *
675
+ * @param flagValue - CLI flag value (highest priority).
676
+ * @param envValue - Environment variable value.
677
+ * @param fileValue - Value from jeeves.config.json.
678
+ * @param defaultValue - Built-in default (lowest priority).
679
+ * @returns The resolved value with provenance annotation.
680
+ */
681
+ function resolveConfigValue(flagValue, envValue, fileValue, defaultValue) {
682
+ if (flagValue !== undefined)
683
+ return { value: flagValue, provenance: 'flag' };
684
+ if (envValue !== undefined)
685
+ return { value: envValue, provenance: 'env' };
686
+ if (fileValue !== undefined)
687
+ return { value: fileValue, provenance: 'file' };
688
+ return { value: defaultValue, provenance: 'default' };
689
+ }
690
+ /**
691
+ * Generate a JSON Schema for the workspace config.
692
+ *
693
+ * @returns A JSON Schema object.
694
+ */
695
+ function generateWorkspaceJsonSchema() {
696
+ return {
697
+ $schema: 'http://json-schema.org/draft-07/schema#',
698
+ title: 'Jeeves Workspace Configuration',
699
+ type: 'object',
700
+ properties: {
701
+ $schema: { type: 'string' },
702
+ core: {
703
+ type: 'object',
704
+ properties: {
705
+ workspace: {
706
+ type: 'string',
707
+ default: WORKSPACE_CONFIG_DEFAULTS.core.workspace,
708
+ },
709
+ configRoot: {
710
+ type: 'string',
711
+ default: WORKSPACE_CONFIG_DEFAULTS.core.configRoot,
712
+ },
713
+ gatewayUrl: {
714
+ type: 'string',
715
+ default: WORKSPACE_CONFIG_DEFAULTS.core.gatewayUrl,
716
+ },
717
+ },
718
+ },
719
+ memory: {
720
+ type: 'object',
721
+ properties: {
722
+ budget: {
723
+ type: 'integer',
724
+ minimum: 1,
725
+ default: WORKSPACE_CONFIG_DEFAULTS.memory.budget,
726
+ },
727
+ warningThreshold: {
728
+ type: 'number',
729
+ minimum: 0,
730
+ maximum: 1,
731
+ default: WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold,
732
+ },
733
+ staleDays: {
734
+ type: 'integer',
735
+ minimum: 1,
736
+ default: WORKSPACE_CONFIG_DEFAULTS.memory.staleDays,
737
+ },
738
+ },
739
+ },
740
+ },
741
+ };
742
+ }
743
+
744
+ /**
745
+ * Shared CLI defaults and resolution for Jeeves CLI commands.
746
+ *
747
+ * @remarks
748
+ * All root CLI commands share workspace/config-root resolution. Values follow
749
+ * the shared precedence model: flags → env → jeeves.config.json → defaults.
750
+ */
751
+ /** Default workspace path. */
752
+ const DEFAULT_WORKSPACE = WORKSPACE_CONFIG_DEFAULTS.core.workspace;
753
+ /** Default config root path. */
754
+ const DEFAULT_CONFIG_ROOT = WORKSPACE_CONFIG_DEFAULTS.core.configRoot;
755
+ /** Read a numeric env var or return undefined if missing, empty, or invalid. */
756
+ function readNumericEnv(name) {
757
+ const raw = process.env[name];
758
+ if (raw === undefined || raw.trim() === '')
759
+ return undefined;
760
+ const value = Number(raw);
761
+ return Number.isFinite(value) ? value : undefined;
762
+ }
763
+ /**
764
+ * Resolve shared CLI config using flags, env, file, and defaults.
765
+ *
766
+ * @param opts - Parsed CLI workspace/config-root options.
767
+ * @returns Resolved config tree with provenance on every leaf.
768
+ */
769
+ function resolveCliConfig(opts) {
770
+ const workspaceSeed = resolveConfigValue(opts.workspace, process.env['JEEVES_WORKSPACE'], undefined, DEFAULT_WORKSPACE);
771
+ const fileConfig = loadWorkspaceConfig(workspaceSeed.value);
772
+ return {
773
+ core: {
774
+ workspace: resolveConfigValue(opts.workspace, process.env['JEEVES_WORKSPACE'], fileConfig?.core?.workspace, DEFAULT_WORKSPACE),
775
+ configRoot: resolveConfigValue(opts.configRoot, process.env['JEEVES_CONFIG_ROOT'], fileConfig?.core?.configRoot, DEFAULT_CONFIG_ROOT),
776
+ gatewayUrl: resolveConfigValue(undefined, process.env['JEEVES_GATEWAY_URL'], fileConfig?.core?.gatewayUrl, WORKSPACE_CONFIG_DEFAULTS.core.gatewayUrl),
777
+ },
778
+ memory: {
779
+ budget: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_BUDGET'), fileConfig?.memory?.budget, WORKSPACE_CONFIG_DEFAULTS.memory.budget),
780
+ warningThreshold: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_WARNING_THRESHOLD'), fileConfig?.memory?.warningThreshold, WORKSPACE_CONFIG_DEFAULTS.memory.warningThreshold),
781
+ staleDays: resolveConfigValue(undefined, readNumericEnv('JEEVES_MEMORY_STALE_DAYS'), fileConfig?.memory?.staleDays, WORKSPACE_CONFIG_DEFAULTS.memory.staleDays),
782
+ },
783
+ };
784
+ }
785
+ /**
786
+ * Initialize core from standard CLI options after resolving shared defaults.
787
+ *
788
+ * @param opts - Parsed Commander options with workspace and configRoot.
789
+ * @returns Resolved CLI config.
790
+ */
791
+ function initFromOptions(opts) {
792
+ const resolved = resolveCliConfig(opts);
793
+ init({
794
+ workspacePath: resolve(resolved.core.workspace.value),
795
+ configRoot: resolve(resolved.core.configRoot.value),
796
+ });
797
+ return resolved;
798
+ }
799
+
800
+ /**
801
+ * `jeeves config [jsonpath]` — inspect effective shared CLI configuration.
802
+ *
803
+ * @remarks
804
+ * Shows effective values and provenance using the shared precedence model.
805
+ * Optional JSONPath filters the resolved config tree.
806
+ */
807
+ /**
808
+ * Build the effective shared CLI config tree.
809
+ *
810
+ * @param opts - Parsed CLI workspace/config-root options.
811
+ * @returns Effective config tree with provenance on each leaf.
812
+ */
813
+ function buildEffectiveConfig(opts) {
814
+ return initFromOptions(opts);
815
+ }
816
+
560
817
  function getDefaultExportFromCjs (x) {
561
818
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
562
819
  }
@@ -1121,6 +1378,129 @@ function buildWithSections(beforeContent, userContent, sections, markers, coreVe
1121
1378
  return parts.join('\n');
1122
1379
  }
1123
1380
 
1381
+ var skillContent = `---
1382
+ name: jeeves
1383
+ description: Jeeves platform architecture, data flow, component interaction, scripts repo, and coordination knowledge. Use when making architectural decisions, coordinating across components, checking platform health, managing service lifecycle, or working with the scripts repo.
1384
+ ---
1385
+
1386
+ # Jeeves Platform Skill
1387
+
1388
+ ## Platform Architecture
1389
+
1390
+ Jeeves is a four-component platform coordinated by a shared library (\`@karmaniverous/jeeves\`):
1391
+
1392
+ | Component | Role | Port |
1393
+ |-----------|------|------|
1394
+ | **jeeves-runner** | Execute: scheduled jobs, SQLite state, HTTP API | 1937 |
1395
+ | **jeeves-watcher** | Index: file→Qdrant semantic indexing, inference rules | 1936 |
1396
+ | **jeeves-server** | Present: web UI, file browser, doc render, export | 1934 |
1397
+ | **jeeves-meta** | Distill: LLM synthesis, .meta/ directories, scheduling | 1938 |
1398
+
1399
+ Core (\`@karmaniverous/jeeves\`) is a **library + CLI**, not a service. No port.
1400
+
1401
+ ## Data Flow
1402
+
1403
+ \`\`\`
1404
+ Files → Watcher (index) → Qdrant → Meta (synthesize) → .meta/ → Watcher (re-index)
1405
+ ↓
1406
+ Runner (schedule) → Scripts → Services ← Server (present) ← Browser
1407
+ \`\`\`
1408
+
1409
+ ## Component Interaction
1410
+
1411
+ - **Watcher** indexes files into Qdrant with inference rules and enrichments.
1412
+ - **Meta** reads from Qdrant, synthesizes \`.meta/\` directories, which watcher re-indexes.
1413
+ - **Runner** executes scheduled scripts that may call any service's HTTP API.
1414
+ - **Server** presents files, renders documents, and provides the event gateway.
1415
+ - **Core** provides shared content management (TOOLS.md, SOUL.md, AGENTS.md), service discovery, config resolution, and the component SDK.
1416
+
1417
+ ## Service Discovery
1418
+
1419
+ Services find each other via config resolution:
1420
+ 1. Component's own config file (\`{configRoot}/jeeves-{name}/config.json\`)
1421
+ 2. Core config file (\`{configRoot}/jeeves-core/config.json\`)
1422
+ 3. Default port constants
1423
+
1424
+ ## Scripts Repo
1425
+
1426
+ Location: \`{configRoot}/jeeves-core/scripts/\`
1427
+ Template: \`@karmaniverous/jeeves-scripts-template\`
1428
+
1429
+ Scripts use utilities from \`@karmaniverous/jeeves\` (general) and \`@karmaniverous/jeeves-runner\` (runner-specific). Any script that could be useful outside runner scheduling belongs in core.
1430
+
1431
+ ## Managed Content System
1432
+
1433
+ Core maintains managed sections in workspace files using comment markers:
1434
+ - **TOOLS.md** — Component sections (section mode) + Platform section
1435
+ - **SOUL.md** — Professional discipline and behavioral foundations (block mode)
1436
+ - **AGENTS.md** — Operational protocols and memory architecture (block mode)
1437
+ - **HEARTBEAT.md** — Platform health status (heading-based)
1438
+
1439
+ Managed blocks are stationary after initial insertion. Cleanup detection uses Jaccard similarity on 3-word shingles. Cleanup escalation spawns a gateway session when orphaned content is detected.
1440
+
1441
+ ## Workspace Configuration
1442
+
1443
+ \`jeeves.config.json\` at workspace root provides shared defaults:
1444
+ - Precedence: CLI flags → env vars → file → defaults
1445
+ - Namespaced: \`core.*\` (workspace, configRoot, gatewayUrl) and \`memory.*\` (budget, warningThreshold, staleDays)
1446
+ - Inspect with \`jeeves config [jsonpath]\`
1447
+
1448
+ ## HEARTBEAT Protocol
1449
+
1450
+ The HEARTBEAT system uses a state machine per component:
1451
+ \`not_installed → deps_missing → config_missing → service_not_installed → service_stopped → healthy\`
1452
+
1453
+ Dependency-aware: hard deps block alerts, soft deps add informational notes. Declined components are tracked via heading suffix.
1454
+
1455
+ ## Plugin Lifecycle
1456
+
1457
+ \`\`\`bash
1458
+ # Core install (seed workspace content)
1459
+ npx @karmaniverous/jeeves install
1460
+
1461
+ # Component plugin install
1462
+ npx @karmaniverous/jeeves-{component}-openclaw install
1463
+
1464
+ # Component plugin uninstall
1465
+ npx @karmaniverous/jeeves-{component}-openclaw uninstall
1466
+
1467
+ # Core uninstall (remove managed sections)
1468
+ npx @karmaniverous/jeeves uninstall
1469
+ \`\`\`
1470
+
1471
+ ## Memory Hygiene
1472
+
1473
+ MEMORY.md has a character budget (default 20,000). Core tracks:
1474
+ - Character count and usage percentage
1475
+ - Warning at 80% of budget
1476
+ - Stale section candidates (H2 sections whose most recent ISO date exceeds the staleness threshold)
1477
+ - Evergreen sections (no dates) are never flagged
1478
+
1479
+ Review is human/agent-mediated — core does not auto-delete.
1480
+ `;
1481
+
1482
+ /**
1483
+ * Skill seeding: write the `jeeves` workspace skill unconditionally.
1484
+ *
1485
+ * @remarks
1486
+ * The skill file is entirely generated — no user-authored content (Decision 48).
1487
+ * Every installer (core CLI and component plugins) writes it unconditionally.
1488
+ * Content is inlined at build time via `rollup-plugin-md.ts`.
1489
+ */
1490
+ /**
1491
+ * Seed the jeeves workspace skill file.
1492
+ *
1493
+ * @param workspacePath - Workspace root directory.
1494
+ */
1495
+ function seedSkill(workspacePath) {
1496
+ const skillDir = join(workspacePath, SKILLS_DIR, JEEVES_SKILL_DIR);
1497
+ if (!existsSync(skillDir)) {
1498
+ mkdirSync(skillDir, { recursive: true });
1499
+ }
1500
+ const skillPath = join(skillDir, 'SKILL.md');
1501
+ writeFileSync(skillPath, skillContent, 'utf-8');
1502
+ }
1503
+
1124
1504
  /**
1125
1505
  * OpenClaw configuration helpers for plugin CLI installers.
1126
1506
  *
@@ -1360,7 +1740,7 @@ function createPluginCli(options) {
1360
1740
  for (const msg of messages) {
1361
1741
  console.log(` ✓ ${msg}`);
1362
1742
  }
1363
- // 4. Write initial HEARTBEAT entry
1743
+ // 4. Write initial HEARTBEAT entry and seed jeeves skill
1364
1744
  try {
1365
1745
  const cfgRoot = opts.configRoot;
1366
1746
  const agents = config.agents;
@@ -1390,10 +1770,17 @@ function createPluginCli(options) {
1390
1770
  catch {
1391
1771
  console.log(' ⚠ Could not write HEARTBEAT entry');
1392
1772
  }
1773
+ try {
1774
+ seedSkill(ws);
1775
+ console.log(' ✓ Jeeves skill seeded');
1776
+ }
1777
+ catch {
1778
+ console.log(' ⚠ Could not seed Jeeves skill');
1779
+ }
1393
1780
  }
1394
1781
  }
1395
1782
  catch {
1396
- // HEARTBEAT is best-effort during install
1783
+ // HEARTBEAT + skill seeding are best-effort during install
1397
1784
  }
1398
1785
  // 5. Write component version
1399
1786
  try {
@@ -2045,19 +2432,6 @@ function createServiceManager(descriptor) {
2045
2432
  }
2046
2433
  }
2047
2434
 
2048
- /**
2049
- * Shared CLI defaults and option registration for Jeeves CLI commands.
2050
- *
2051
- * @remarks
2052
- * All three CLI commands (install, uninstall, status) share the same
2053
- * `--workspace` and `--config-root` options with the same defaults.
2054
- * This module centralizes them to eliminate duplication.
2055
- */
2056
- /** Default workspace path (current directory). */
2057
- const DEFAULT_WORKSPACE = '.';
2058
- /** Default config root path. */
2059
- const DEFAULT_CONFIG_ROOT = './config';
2060
-
2061
2435
  /**
2062
2436
  * Factory for the standard Jeeves service CLI.
2063
2437
  *
@@ -2486,10 +2860,10 @@ async function updateManagedSection(filePath, content, options = {}) {
2486
2860
  ? `# ${markers.title}\n\n${sectionText}`
2487
2861
  : sectionText;
2488
2862
  }
2489
- // Combine beforeContent + userContent for the user zone.
2490
- // When migrating from top→bottom, beforeContent is empty and
2491
- // userContent has the real content. When already at bottom,
2492
- // beforeContent has the user content and userContent is empty.
2863
+ // Build the full managed block
2864
+ const beginLine = formatBeginMarker(markers.begin, coreVersion);
2865
+ const endLine = formatEndMarker(markers.end);
2866
+ // Combine all user content for cleanup detection
2493
2867
  const rawUserContent = [parsed.beforeContent, parsed.userContent]
2494
2868
  .filter(Boolean)
2495
2869
  .join('\n\n')
@@ -2497,9 +2871,6 @@ async function updateManagedSection(filePath, content, options = {}) {
2497
2871
  // Strip foreign managed blocks from user content (cross-contamination fix)
2498
2872
  const userContent = stripForeignMarkers(rawUserContent, markers);
2499
2873
  const cleanupNeeded = needsCleanup(newManagedBody, userContent);
2500
- // Build the full managed block
2501
- const beginLine = formatBeginMarker(markers.begin, coreVersion);
2502
- const endLine = formatEndMarker(markers.end);
2503
2874
  const managedParts = [];
2504
2875
  managedParts.push(beginLine);
2505
2876
  if (cleanupNeeded) {
@@ -2511,26 +2882,54 @@ async function updateManagedSection(filePath, content, options = {}) {
2511
2882
  managedParts.push('');
2512
2883
  managedParts.push(endLine);
2513
2884
  const managedBlock = managedParts.join('\n');
2514
- const position = markers.position ?? 'top';
2515
- const fileParts = [];
2516
- if (position === 'bottom') {
2517
- // User content first, managed block at end
2518
- if (userContent) {
2519
- fileParts.push(userContent);
2885
+ let newFileContent;
2886
+ if (parsed.found) {
2887
+ // Existing block: update in place — preserve position, don't move.
2888
+ // Strip foreign managed blocks from both content zones (cross-contamination fix).
2889
+ const cleanBefore = stripForeignMarkers(parsed.beforeContent, markers);
2890
+ const cleanAfter = stripForeignMarkers(parsed.userContent, markers);
2891
+ const fileParts = [];
2892
+ if (cleanBefore) {
2893
+ fileParts.push(cleanBefore);
2520
2894
  fileParts.push('');
2521
2895
  }
2522
2896
  fileParts.push(managedBlock);
2897
+ if (cleanAfter) {
2898
+ fileParts.push('');
2899
+ fileParts.push(cleanAfter);
2900
+ }
2901
+ fileParts.push('');
2902
+ newFileContent = fileParts.join('\n');
2523
2903
  }
2524
2904
  else {
2525
- // Managed block first (legacy default), user content below
2526
- fileParts.push(managedBlock);
2527
- if (userContent) {
2528
- fileParts.push('');
2529
- fileParts.push(userContent);
2905
+ // No existing block: insert new block using the configured position.
2906
+ // Strip orphaned same-type BEGIN markers from user content to prevent
2907
+ // the parser from pairing them with the new END marker on the next cycle.
2908
+ const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2909
+ const orphanedBeginRe = new RegExp(`^<!--\\s*${escapedBegin}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$\\n?`, 'gm');
2910
+ const cleanUserContent = userContent
2911
+ .replace(orphanedBeginRe, '')
2912
+ .replace(/\n{3,}/g, '\n\n')
2913
+ .trim();
2914
+ const position = markers.position ?? 'top';
2915
+ const fileParts = [];
2916
+ if (position === 'bottom') {
2917
+ if (cleanUserContent) {
2918
+ fileParts.push(cleanUserContent);
2919
+ fileParts.push('');
2920
+ }
2921
+ fileParts.push(managedBlock);
2530
2922
  }
2923
+ else {
2924
+ fileParts.push(managedBlock);
2925
+ if (cleanUserContent) {
2926
+ fileParts.push('');
2927
+ fileParts.push(cleanUserContent);
2928
+ }
2929
+ }
2930
+ fileParts.push('');
2931
+ newFileContent = fileParts.join('\n');
2531
2932
  }
2532
- fileParts.push('');
2533
- const newFileContent = fileParts.join('\n');
2534
2933
  atomicWrite(filePath, newFileContent);
2535
2934
  });
2536
2935
  }
@@ -3024,6 +3423,103 @@ async function refreshPlatformContent(options) {
3024
3423
  copyTemplates(coreConfigDir);
3025
3424
  }
3026
3425
 
3426
+ /**
3427
+ * Cleanup-session escalation for managed files with orphaned duplicated content.
3428
+ *
3429
+ * @remarks
3430
+ * When a managed file contains the cleanup flag, the writer can ask the
3431
+ * OpenClaw gateway to spawn a background session to remove orphaned content.
3432
+ * The request is best-effort: accepted requests return `true`; any transport
3433
+ * or HTTP failure returns `false` so the file warning remains the fallback.
3434
+ */
3435
+ /** Timeout for cleanup-session spawn requests. */
3436
+ const CLEANUP_REQUEST_TIMEOUT_MS = 5_000;
3437
+ /**
3438
+ * Build the cleanup task prompt sent to the gateway session API.
3439
+ *
3440
+ * @param filePath - Managed file requiring cleanup.
3441
+ * @param markerIdentity - Marker identity for the file.
3442
+ * @returns Cleanup instructions for the spawned session.
3443
+ */
3444
+ function buildCleanupTask(filePath, markerIdentity) {
3445
+ return [
3446
+ `Clean up orphaned managed content in ${filePath}.`,
3447
+ `The file uses ${markerIdentity} managed comment markers.`,
3448
+ 'Review content outside the managed block and remove only duplicated managed content.',
3449
+ 'Preserve any unique user-authored content outside the managed block.',
3450
+ 'Do not modify content inside the managed block unless required to preserve valid marker structure.',
3451
+ ].join(' ');
3452
+ }
3453
+ /**
3454
+ * Request a cleanup session from the OpenClaw gateway.
3455
+ *
3456
+ * @remarks
3457
+ * Fire-and-forget. A 200-class response means the request was accepted.
3458
+ * Any HTTP or transport failure returns `false` so the file-level cleanup
3459
+ * warning remains the only signal.
3460
+ *
3461
+ * @param options - Cleanup request configuration.
3462
+ * @returns Whether the gateway accepted the cleanup request.
3463
+ */
3464
+ async function requestCleanupSession(options) {
3465
+ const { gatewayUrl, filePath, markerIdentity } = options;
3466
+ const url = `${gatewayUrl.replace(/\/$/, '')}/sessions/spawn`;
3467
+ const label = `cleanup:${basename(filePath)}`;
3468
+ const body = {
3469
+ task: buildCleanupTask(filePath, markerIdentity),
3470
+ label,
3471
+ };
3472
+ try {
3473
+ const response = await fetchWithTimeout(url, CLEANUP_REQUEST_TIMEOUT_MS, {
3474
+ method: 'POST',
3475
+ headers: { 'Content-Type': 'application/json' },
3476
+ body: JSON.stringify(body),
3477
+ });
3478
+ return response.ok;
3479
+ }
3480
+ catch {
3481
+ return false;
3482
+ }
3483
+ }
3484
+
3485
+ /**
3486
+ * Cleanup flag scanning extracted from ComponentWriter.cycle().
3487
+ *
3488
+ * @remarks
3489
+ * After writing managed files, scans each for the cleanup flag and
3490
+ * fires a best-effort escalation request when a gateway URL is configured.
3491
+ * Uses a `pendingCleanups` set to deduplicate in-flight requests.
3492
+ */
3493
+ /**
3494
+ * Scan managed files for the cleanup flag and escalate when detected.
3495
+ *
3496
+ * @param targets - Managed files to scan.
3497
+ * @param gatewayUrl - Gateway URL for session spawn.
3498
+ * @param pendingCleanups - Set tracking in-flight requests (mutated).
3499
+ */
3500
+ function scanAndEscalateCleanup(targets, gatewayUrl, pendingCleanups) {
3501
+ for (const target of targets) {
3502
+ try {
3503
+ if (pendingCleanups.has(target.filePath))
3504
+ continue;
3505
+ const fileContent = readFileSync(target.filePath, 'utf-8');
3506
+ if (fileContent.includes(CLEANUP_FLAG)) {
3507
+ pendingCleanups.add(target.filePath);
3508
+ void requestCleanupSession({
3509
+ gatewayUrl,
3510
+ filePath: target.filePath,
3511
+ markerIdentity: target.markerIdentity,
3512
+ }).finally(() => {
3513
+ pendingCleanups.delete(target.filePath);
3514
+ });
3515
+ }
3516
+ }
3517
+ catch {
3518
+ // Best-effort: don't fail the cycle for escalation issues.
3519
+ }
3520
+ }
3521
+ }
3522
+
3027
3523
  /**
3028
3524
  * Core configuration schema and resolution.
3029
3525
  *
@@ -3468,29 +3964,76 @@ async function orchestrateHeartbeat(options) {
3468
3964
  }
3469
3965
 
3470
3966
  /**
3471
- * Timer-based orchestrator for managed content writing.
3967
+ * HEARTBEAT orchestration extracted from ComponentWriter.cycle().
3472
3968
  *
3473
3969
  * @remarks
3474
- * `ComponentWriter` manages a component's TOOLS.md section writes
3475
- * and platform content maintenance (SOUL.md, AGENTS.md, Platform section)
3476
- * on a configurable prime-interval timer cycle.
3970
+ * Reads existing HEARTBEAT.md, resolves declined components, runs the
3971
+ * heartbeat state machine, and writes the result. Best-effort: failures
3972
+ * are logged but do not propagate.
3973
+ */
3974
+ /**
3975
+ * Read a file's content, returning empty string if the file does not exist.
3976
+ *
3977
+ * @param filePath - Absolute file path.
3978
+ * @returns File content or empty string.
3979
+ */
3980
+ function readFileOrEmpty(filePath) {
3981
+ try {
3982
+ return readFileSync(filePath, 'utf-8');
3983
+ }
3984
+ catch (err) {
3985
+ if (err instanceof Error &&
3986
+ 'code' in err &&
3987
+ err.code === 'ENOENT') {
3988
+ return '';
3989
+ }
3990
+ throw err;
3991
+ }
3992
+ }
3993
+ /**
3994
+ * Run a single HEARTBEAT orchestration cycle.
3995
+ *
3996
+ * @param options - Heartbeat cycle configuration.
3477
3997
  */
3998
+ async function runHeartbeatCycle(options) {
3999
+ const { workspacePath, coreConfigDir, configRoot } = options;
4000
+ const heartbeatPath = join(workspacePath, WORKSPACE_FILES.heartbeat);
4001
+ try {
4002
+ const existingContent = readFileOrEmpty(heartbeatPath);
4003
+ const parsed = parseHeartbeat(existingContent);
4004
+ const declinedNames = new Set(parsed.entries.filter((e) => e.declined).map((e) => e.name));
4005
+ const entries = await orchestrateHeartbeat({
4006
+ coreConfigDir,
4007
+ configRoot,
4008
+ declinedNames,
4009
+ });
4010
+ await writeHeartbeatSection(heartbeatPath, entries);
4011
+ }
4012
+ catch (err) {
4013
+ const msg = err instanceof Error ? err.message : String(err);
4014
+ console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${msg}`);
4015
+ }
4016
+ }
4017
+
3478
4018
  /**
3479
- * Orchestrates managed content writing for a single Jeeves component.
4019
+ * Timer-based orchestrator for managed content writing.
3480
4020
  *
3481
4021
  * @remarks
3482
- * Created via `createComponentWriter()`. Manages a timer that fires
3483
- * at the component's prime-interval, calling `generateToolsContent()`
3484
- * and `refreshPlatformContent()` on each cycle.
4022
+ * `ComponentWriter` manages a component's TOOLS.md section writes
4023
+ * and platform content maintenance (SOUL.md, AGENTS.md, Platform section)
4024
+ * on a configurable prime-interval timer cycle.
3485
4025
  */
3486
4026
  class ComponentWriter {
3487
4027
  timer;
3488
4028
  component;
3489
4029
  configDir;
4030
+ gatewayUrl;
4031
+ pendingCleanups = new Set();
3490
4032
  /** @internal */
3491
- constructor(component) {
4033
+ constructor(component, options) {
3492
4034
  this.component = component;
3493
4035
  this.configDir = getComponentConfigDir(component.name);
4036
+ this.gatewayUrl = options?.gatewayUrl;
3494
4037
  }
3495
4038
  /** The component's config directory path. */
3496
4039
  get componentConfigDir() {
@@ -3524,15 +4067,16 @@ class ComponentWriter {
3524
4067
  * Execute a single write cycle.
3525
4068
  *
3526
4069
  * @remarks
3527
- * Calls `generateToolsContent()` and writes the component's
3528
- * TOOLS.md section via `updateManagedSection()`. Also calls
3529
- * `refreshPlatformContent()` for shared content maintenance.
4070
+ * 1. Write the component's TOOLS.md section.
4071
+ * 2. Refresh shared platform content (SOUL.md, AGENTS.md, Platform section).
4072
+ * 3. Scan for cleanup flags and escalate if a gateway URL is configured.
4073
+ * 4. Run HEARTBEAT health orchestration.
3530
4074
  */
3531
4075
  async cycle() {
3532
4076
  try {
3533
4077
  const workspacePath = getWorkspacePath();
3534
4078
  const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
3535
- // Write the component's TOOLS.md section
4079
+ // 1. Write the component's TOOLS.md section
3536
4080
  const toolsContent = this.component.generateToolsContent();
3537
4081
  await updateManagedSection(toolsPath, toolsContent, {
3538
4082
  mode: 'section',
@@ -3540,7 +4084,7 @@ class ComponentWriter {
3540
4084
  markers: TOOLS_MARKERS,
3541
4085
  coreVersion: CORE_VERSION,
3542
4086
  });
3543
- // Platform content maintenance: SOUL.md, AGENTS.md, Platform section
4087
+ // 2. Platform content maintenance
3544
4088
  await refreshPlatformContent({
3545
4089
  coreVersion: CORE_VERSION,
3546
4090
  componentName: this.component.name,
@@ -3548,36 +4092,26 @@ class ComponentWriter {
3548
4092
  servicePackage: this.component.servicePackage,
3549
4093
  pluginPackage: this.component.pluginPackage,
3550
4094
  });
3551
- // HEARTBEAT health orchestration
3552
- const heartbeatPath = join(workspacePath, WORKSPACE_FILES.heartbeat);
3553
- try {
3554
- const existingContent = (() => {
3555
- try {
3556
- return readFileSync(heartbeatPath, 'utf-8');
3557
- }
3558
- catch (err) {
3559
- // Only swallow "file not found" — let permission errors propagate
3560
- if (err instanceof Error &&
3561
- 'code' in err &&
3562
- err.code === 'ENOENT') {
3563
- return '';
3564
- }
3565
- throw err;
3566
- }
3567
- })();
3568
- const parsed = parseHeartbeat(existingContent);
3569
- const declinedNames = new Set(parsed.entries.filter((e) => e.declined).map((e) => e.name));
3570
- const entries = await orchestrateHeartbeat({
3571
- coreConfigDir: getCoreConfigDir(),
3572
- configRoot: getConfigRoot(),
3573
- declinedNames,
3574
- });
3575
- await writeHeartbeatSection(heartbeatPath, entries);
3576
- }
3577
- catch (err) {
3578
- const msg = err instanceof Error ? err.message : String(err);
3579
- console.warn(`jeeves-core: HEARTBEAT orchestration failed: ${msg}`);
4095
+ // 3. Cleanup escalation
4096
+ if (this.gatewayUrl) {
4097
+ scanAndEscalateCleanup([
4098
+ { filePath: toolsPath, markerIdentity: 'TOOLS' },
4099
+ {
4100
+ filePath: join(workspacePath, WORKSPACE_FILES.soul),
4101
+ markerIdentity: 'SOUL',
4102
+ },
4103
+ {
4104
+ filePath: join(workspacePath, WORKSPACE_FILES.agents),
4105
+ markerIdentity: 'AGENTS',
4106
+ },
4107
+ ], this.gatewayUrl, this.pendingCleanups);
3580
4108
  }
4109
+ // 4. HEARTBEAT orchestration
4110
+ await runHeartbeatCycle({
4111
+ workspacePath,
4112
+ coreConfigDir: getCoreConfigDir(),
4113
+ configRoot: getConfigRoot(),
4114
+ });
3581
4115
  }
3582
4116
  catch (err) {
3583
4117
  const message = err instanceof Error ? err.message : String(err);
@@ -3660,13 +4194,14 @@ function createAsyncContentCache(options) {
3660
4194
  * This replaces the v0.4.0 `createComponentWriter(JeevesComponent)`.
3661
4195
  *
3662
4196
  * @param descriptor - The component descriptor to validate and wrap.
4197
+ * @param options - Optional writer configuration (e.g., gatewayUrl for cleanup escalation).
3663
4198
  * @returns A new `ComponentWriter` instance.
3664
4199
  * @throws ZodError if the descriptor is invalid.
3665
4200
  */
3666
- function createComponentWriter(descriptor) {
4201
+ function createComponentWriter(descriptor, options) {
3667
4202
  // Validate via Zod — throws ZodError with detailed messages on failure
3668
4203
  jeevesComponentDescriptorSchema.parse(descriptor);
3669
- return new ComponentWriter(descriptor);
4204
+ return new ComponentWriter(descriptor, options);
3670
4205
  }
3671
4206
 
3672
4207
  /**
@@ -3707,6 +4242,92 @@ function getBindAddress(componentName) {
3707
4242
  return DEFAULT_BIND_ADDRESS;
3708
4243
  }
3709
4244
 
4245
+ /**
4246
+ * Memory budget accounting and staleness detection for MEMORY.md.
4247
+ *
4248
+ * @remarks
4249
+ * Scans MEMORY.md for ISO date patterns in H2/H3 headings and bullet items.
4250
+ * Reports character count against a configured budget, warning threshold state,
4251
+ * and stale section candidates. Does not auto-delete: review remains
4252
+ * human- or agent-mediated (Decision 42).
4253
+ */
4254
+ /** ISO date pattern: YYYY-MM-DD. */
4255
+ const ISO_DATE_RE = /\b(\d{4}-\d{2}-\d{2})\b/g;
4256
+ /** H2 heading pattern used to split sections. */
4257
+ const H2_RE = /^## /m;
4258
+ /**
4259
+ * Extract the most recent ISO date from a string.
4260
+ *
4261
+ * @param text - Text to scan for dates.
4262
+ * @returns The most recent date found, or undefined.
4263
+ */
4264
+ function extractMostRecentDate(text) {
4265
+ const matches = text.match(ISO_DATE_RE);
4266
+ if (!matches)
4267
+ return undefined;
4268
+ let latest;
4269
+ for (const match of matches) {
4270
+ const d = new Date(match + 'T00:00:00Z');
4271
+ if (!Number.isNaN(d.getTime())) {
4272
+ if (!latest || d > latest)
4273
+ latest = d;
4274
+ }
4275
+ }
4276
+ return latest;
4277
+ }
4278
+ /**
4279
+ * Analyze MEMORY.md for budget and staleness.
4280
+ *
4281
+ * @param options - Analysis configuration.
4282
+ * @returns Memory hygiene result.
4283
+ */
4284
+ function analyzeMemory(options) {
4285
+ const { workspacePath, budget, warningThreshold, staleDays } = options;
4286
+ const memoryPath = join(workspacePath, WORKSPACE_FILES.memory);
4287
+ if (!existsSync(memoryPath)) {
4288
+ return {
4289
+ exists: false,
4290
+ charCount: 0,
4291
+ budget,
4292
+ usage: 0,
4293
+ warning: false,
4294
+ overBudget: false,
4295
+ staleCandidates: 0,
4296
+ staleSectionNames: [],
4297
+ };
4298
+ }
4299
+ const content = readFileSync(memoryPath, 'utf-8');
4300
+ const charCount = content.length;
4301
+ const usage = budget > 0 ? charCount / budget : charCount > 0 ? Infinity : 0;
4302
+ const warning = usage >= warningThreshold;
4303
+ const overBudget = usage > 1;
4304
+ // Split into H2 sections and scan for staleness
4305
+ const sections = content.split(H2_RE).slice(1); // skip content before first H2
4306
+ const now = Date.now();
4307
+ const thresholdMs = staleDays * 24 * 60 * 60 * 1000;
4308
+ const staleSectionNames = [];
4309
+ for (const section of sections) {
4310
+ const sectionName = section.split('\n')[0]?.trim() ?? '';
4311
+ const recentDate = extractMostRecentDate(section);
4312
+ // Sections without dates are evergreen — never flagged (Decision 47)
4313
+ if (!recentDate)
4314
+ continue;
4315
+ if (now - recentDate.getTime() > thresholdMs) {
4316
+ staleSectionNames.push(sectionName);
4317
+ }
4318
+ }
4319
+ return {
4320
+ exists: true,
4321
+ charCount,
4322
+ budget,
4323
+ usage,
4324
+ warning,
4325
+ overBudget,
4326
+ staleCandidates: staleSectionNames.length,
4327
+ staleSectionNames,
4328
+ };
4329
+ }
4330
+
3710
4331
  /**
3711
4332
  * One-shot content seeding used by the CLI install command.
3712
4333
  *
@@ -3765,6 +4386,8 @@ async function seedContent(options) {
3765
4386
  content: `- ${NOT_INSTALLED_ALERTS[name]}`,
3766
4387
  }));
3767
4388
  await writeHeartbeatSection(heartbeatPath, entries);
4389
+ // Seed jeeves workspace skill (Decision 48: overwrite-on-install)
4390
+ seedSkill(getWorkspacePath());
3768
4391
  }
3769
4392
 
3770
4393
  /**
@@ -4468,4 +5091,4 @@ async function getChannelWorkspace(channelId, token, options) {
4468
5091
  return teamId;
4469
5092
  }
4470
5093
 
4471
- 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, META_PORT, PLATFORM_COMPONENTS, REGISTRY_CACHE_FILE, RUNNER_PORT, SECTION_IDS, SECTION_ORDER, SERVER_PORT, SOUL_MARKERS, STALENESS_THRESHOLD_MS, STALE_LOCK_MS, TEMPLATES_DIR, TOOLS_MARKERS, VERSION_STAMP_PATTERN, WATCHER_PORT, WORKSPACE_FILES, appendJsonl, atomicWrite, buildHeartbeatSection, checkRegistryVersion, connectionFail, coreConfigSchema, createAsyncContentCache, createComponentWriter, createConfigApplyHandler, createConfigQueryHandler, createGoogleAuth, createPluginCli, createPluginToolset, createServiceCli, createServiceManager, createStatusHandler, ensureDir, fail, fetchJson, fetchWithTimeout, formatBeginMarker, formatEndMarker, generateJsonSchema, getArg, getBindAddress, getChannelWorkspace, getComponentConfigDir, getConfigRoot, getCoreConfigDir, getCoreConfigFile, getEffectiveServiceName, getPackageVersion, getServiceState, getServiceUrl, getWorkspacePath, init, isPrime, jaccard, jeevesComponentDescriptorSchema, loadEnvFile, needsCleanup, nowIso, ok, orchestrateHeartbeat, parseArgs, parseHeartbeat, parseManaged, patchConfig, postJson, readComponentVersions, readJson, readJsonl, refreshPlatformContent, removeComponentVersion, removeManagedSection, resetInit, resolveConfigPath, resolveOpenClawHome, resolveOptionalPluginSetting, resolvePluginSetting, resolveWorkspacePath, run, runScript, runWithRetry, saveCache, seedContent, shingles, shouldWrite, sleepAsync, sleepMs, updateManagedSection, uuid, withFileLock, writeComponentVersion, writeHeartbeatSection, writeJsonAtomic, writeJsonl };
5094
+ 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, 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, 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, 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 };