@karmaniverous/jeeves 0.5.1 → 0.5.4
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 +4 -4
- package/content/agents-section.md +1 -31
- package/content/skill.md +17 -0
- package/content/soul-section.md +1 -11
- package/dist/cli/jeeves/index.js +90 -71
- package/dist/cli/plugin/index.js +263 -29
- package/dist/cli/service/index.js +36 -27
- package/dist/index.d.ts +80 -26
- package/dist/index.js +2343 -2169
- package/package.json +1 -1
package/dist/cli/plugin/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync, readFileSync, readdirSync, copyFileSync, rmSync } from 'node:fs';
|
|
3
|
-
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { dirname, basename, join, resolve } from 'node:path';
|
|
4
4
|
import * as commander from 'commander';
|
|
5
|
-
import {
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { lock } from 'proper-lockfile';
|
|
7
7
|
import 'semver';
|
|
8
|
+
import 'node:child_process';
|
|
8
9
|
import { homedir } from 'node:os';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { packageDirectorySync } from 'package-directory';
|
|
9
13
|
|
|
10
14
|
function getDefaultExportFromCjs (x) {
|
|
11
15
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -126,14 +130,14 @@ const COMPONENT_VERSIONS_FILE = 'component-versions.json';
|
|
|
126
130
|
* Core library version, inlined at build time.
|
|
127
131
|
*
|
|
128
132
|
* @remarks
|
|
129
|
-
* The `0.5.
|
|
133
|
+
* The `0.5.3` placeholder is replaced by
|
|
130
134
|
* `@rollup/plugin-replace` during the build with the actual version
|
|
131
135
|
* from `package.json`. This ensures the correct version survives
|
|
132
136
|
* when consumers bundle core into their own dist (where runtime
|
|
133
137
|
* `import.meta.url`-based resolution would find the wrong package.json).
|
|
134
138
|
*/
|
|
135
139
|
/** The core library version from package.json (inlined at build time). */
|
|
136
|
-
const CORE_VERSION = '0.5.
|
|
140
|
+
const CORE_VERSION = '0.5.3';
|
|
137
141
|
|
|
138
142
|
/**
|
|
139
143
|
* Shared file I/O helpers for managed section operations.
|
|
@@ -149,27 +153,46 @@ const STALE_LOCK_MS = 120_000;
|
|
|
149
153
|
const DEFAULT_CORE_VERSION = CORE_VERSION;
|
|
150
154
|
/** Lock retry options. */
|
|
151
155
|
const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
|
|
156
|
+
/** Maximum rename retry attempts on EPERM. */
|
|
157
|
+
const ATOMIC_WRITE_MAX_RETRIES = 3;
|
|
158
|
+
/** Delay between EPERM retries in milliseconds. */
|
|
159
|
+
const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
|
|
152
160
|
/**
|
|
153
161
|
* Write content to a file atomically via a temp file + rename.
|
|
154
162
|
*
|
|
163
|
+
* @remarks
|
|
164
|
+
* Retries the rename up to three times on EPERM (Windows file-handle
|
|
165
|
+
* contention) with a 100 ms synchronous delay between attempts.
|
|
166
|
+
*
|
|
155
167
|
* @param filePath - Absolute path to the target file.
|
|
156
168
|
* @param content - Content to write.
|
|
157
169
|
*/
|
|
158
170
|
function atomicWrite(filePath, content) {
|
|
159
171
|
const dir = dirname(filePath);
|
|
160
|
-
const
|
|
172
|
+
const base = basename(filePath, '.md');
|
|
173
|
+
const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
|
|
161
174
|
writeFileSync(tempPath, content, 'utf-8');
|
|
162
|
-
|
|
163
|
-
renameSync(tempPath, filePath);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
175
|
+
for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
|
|
166
176
|
try {
|
|
167
|
-
|
|
177
|
+
renameSync(tempPath, filePath);
|
|
178
|
+
return;
|
|
168
179
|
}
|
|
169
|
-
catch {
|
|
170
|
-
|
|
180
|
+
catch (err) {
|
|
181
|
+
const isEperm = err instanceof Error &&
|
|
182
|
+
'code' in err &&
|
|
183
|
+
err.code === 'EPERM';
|
|
184
|
+
if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
|
|
185
|
+
try {
|
|
186
|
+
unlinkSync(tempPath);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* best-effort cleanup */
|
|
190
|
+
}
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
// Synchronous sleep before retry (acceptable in atomic write context)
|
|
194
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
|
|
171
195
|
}
|
|
172
|
-
throw err;
|
|
173
196
|
}
|
|
174
197
|
}
|
|
175
198
|
/**
|
|
@@ -402,7 +425,7 @@ function parseHeartbeat(fileContent) {
|
|
|
402
425
|
const userContent = fileContent.slice(0, headingIndex).trim();
|
|
403
426
|
const sectionContent = fileContent.slice(headingIndex + HEARTBEAT_HEADING.length);
|
|
404
427
|
const entries = [];
|
|
405
|
-
const h2Re = /^## (jeeves-\S
|
|
428
|
+
const h2Re = /^## (jeeves-\S+?|\S+\.md)(?:: declined)?$/gm;
|
|
406
429
|
let match;
|
|
407
430
|
const h2Positions = [];
|
|
408
431
|
while ((match = h2Re.exec(sectionContent)) !== null) {
|
|
@@ -483,6 +506,15 @@ function sortSectionsByOrder(sections) {
|
|
|
483
506
|
* sections within the block, and returns the structured result plus
|
|
484
507
|
* user content outside the markers.
|
|
485
508
|
*/
|
|
509
|
+
/**
|
|
510
|
+
* Escape a string for safe use as a literal in a RegExp pattern.
|
|
511
|
+
*
|
|
512
|
+
* @param str - The string to escape.
|
|
513
|
+
* @returns The escaped string.
|
|
514
|
+
*/
|
|
515
|
+
function escapeForRegex(str) {
|
|
516
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
517
|
+
}
|
|
486
518
|
/**
|
|
487
519
|
* Build regex patterns for the given markers.
|
|
488
520
|
*
|
|
@@ -490,11 +522,9 @@ function sortSectionsByOrder(sections) {
|
|
|
490
522
|
* @returns Object with begin and end regex patterns.
|
|
491
523
|
*/
|
|
492
524
|
function buildMarkerPatterns(markers) {
|
|
493
|
-
const escapedBegin = markers.begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
494
|
-
const escapedEnd = markers.end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
495
525
|
return {
|
|
496
|
-
beginRe: new RegExp(`^<!--\\s*${
|
|
497
|
-
endRe: new RegExp(`^<!--\\s*${
|
|
526
|
+
beginRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.begin)}(?:\\s*\\|[^>]*)?\\s*(?:—[^>]*)?\\s*-->\\s*$`, 'm'),
|
|
527
|
+
endRe: new RegExp(`^<!--\\s*${escapeForRegex(markers.end)}\\s*-->\\s*$`, 'm'),
|
|
498
528
|
};
|
|
499
529
|
}
|
|
500
530
|
/**
|
|
@@ -819,6 +849,23 @@ Review is human/agent-mediated — core does not auto-delete.
|
|
|
819
849
|
Memory hygiene is checked on every \`ComponentWriter\` cycle alongside component health. When budget or staleness thresholds are breached, a \`## MEMORY.md\` alert appears in HEARTBEAT.md under \`# Jeeves Platform Status\`. The alert includes character count, budget usage percentage, and any stale section names. When memory is healthy, the heading is absent — no alert content, no LLM cost on heartbeat polls.
|
|
820
850
|
|
|
821
851
|
The \`## MEMORY.md\` heading follows the same declined/active lifecycle as component headings (\`## jeeves-{name}\`). Users can decline memory alerts by changing the heading to \`## MEMORY.md: declined\`.
|
|
852
|
+
|
|
853
|
+
## Workspace File Size Monitoring
|
|
854
|
+
|
|
855
|
+
OpenClaw applies a ~20,000-char injection limit to all workspace bootstrap files (AGENTS.md, SOUL.md, TOOLS.md, USER.md, MEMORY.md). Files exceeding the limit are silently truncated.
|
|
856
|
+
|
|
857
|
+
Core monitors all five files on every \`ComponentWriter\` cycle:
|
|
858
|
+
- Warning at 80% of budget (fixed threshold; not configurable via \`jeeves.config.json\`)
|
|
859
|
+
- Over-budget alert when charCount exceeds the budget
|
|
860
|
+
- Missing files are silently skipped
|
|
861
|
+
|
|
862
|
+
### HEARTBEAT Integration
|
|
863
|
+
|
|
864
|
+
When a workspace file exceeds the warning threshold, a \`## {filename}\` alert appears in HEARTBEAT.md (e.g., \`## AGENTS.md\`). The alert includes:
|
|
865
|
+
- Character count, budget, and usage percentage
|
|
866
|
+
- Trimming guidance in priority order: (1) move domain-specific content to a local skill, (2) extract reference material to companion files with a pointer, (3) summarize verbose instructions, (4) remove stale content
|
|
867
|
+
|
|
868
|
+
Each file heading follows the same declined/active lifecycle as component headings. Users can decline alerts by changing the heading to \`## {filename}: declined\` (e.g., \`## AGENTS.md: declined\`).
|
|
822
869
|
`;
|
|
823
870
|
|
|
824
871
|
/**
|
|
@@ -843,6 +890,153 @@ function seedSkill(workspacePath) {
|
|
|
843
890
|
writeFileSync(skillPath, skillContent, 'utf-8');
|
|
844
891
|
}
|
|
845
892
|
|
|
893
|
+
/**
|
|
894
|
+
* Zod schema for the Jeeves component descriptor.
|
|
895
|
+
*
|
|
896
|
+
* @remarks
|
|
897
|
+
* The descriptor replaces the v0.4.0 `JeevesComponent` interface with a
|
|
898
|
+
* Zod-first approach. The TypeScript type is inferred via `z.infer<>`.
|
|
899
|
+
* Validates at parse time: prime interval, callable functions.
|
|
900
|
+
*/
|
|
901
|
+
/**
|
|
902
|
+
* Check whether a number is prime.
|
|
903
|
+
*
|
|
904
|
+
* @param n - Number to check.
|
|
905
|
+
* @returns `true` if n is prime.
|
|
906
|
+
*/
|
|
907
|
+
function isPrime(n) {
|
|
908
|
+
if (n < 2)
|
|
909
|
+
return false;
|
|
910
|
+
if (n === 2)
|
|
911
|
+
return true;
|
|
912
|
+
if (n % 2 === 0)
|
|
913
|
+
return false;
|
|
914
|
+
for (let i = 3; i * i <= n; i += 2) {
|
|
915
|
+
if (n % i === 0)
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Zod schema for the Jeeves component descriptor.
|
|
922
|
+
*
|
|
923
|
+
* @remarks
|
|
924
|
+
* Single source of truth for what a component must provide.
|
|
925
|
+
* Factories consume this descriptor to produce CLI commands,
|
|
926
|
+
* plugin tools, and HTTP handlers.
|
|
927
|
+
*/
|
|
928
|
+
z.object({
|
|
929
|
+
/** Component name (e.g., 'watcher', 'runner', 'server', 'meta'). */
|
|
930
|
+
name: z.string().min(1, 'name must be a non-empty string'),
|
|
931
|
+
/** Component version (from package.json). */
|
|
932
|
+
version: z.string().min(1, 'version must be a non-empty string'),
|
|
933
|
+
/** npm package name for the service. */
|
|
934
|
+
servicePackage: z.string().min(1),
|
|
935
|
+
/** npm package name for the plugin. */
|
|
936
|
+
pluginPackage: z.string().min(1),
|
|
937
|
+
/** System service name. Defaults to `jeeves-${name}` when not provided. */
|
|
938
|
+
serviceName: z.string().min(1).optional(),
|
|
939
|
+
/** Default port for the service's HTTP API. */
|
|
940
|
+
defaultPort: z.number().int().positive(),
|
|
941
|
+
/** Zod schema for validating config files. */
|
|
942
|
+
configSchema: z.custom((val) => val !== null &&
|
|
943
|
+
typeof val === 'object' &&
|
|
944
|
+
typeof val.parse === 'function', { message: 'configSchema must be a Zod schema' }),
|
|
945
|
+
/** Config file name (e.g., 'jeeves-watcher.config.json'). */
|
|
946
|
+
configFileName: z.string().min(1),
|
|
947
|
+
/** Returns a default config object for `init`. */
|
|
948
|
+
initTemplate: z.function({
|
|
949
|
+
input: [],
|
|
950
|
+
output: z.record(z.string(), z.unknown()),
|
|
951
|
+
}),
|
|
952
|
+
/**
|
|
953
|
+
* Service-side callback after config apply. Receives the merged,
|
|
954
|
+
* validated config (not the raw patch). Optional — if omitted,
|
|
955
|
+
* write-only (service picks up changes on restart).
|
|
956
|
+
*/
|
|
957
|
+
onConfigApply: z
|
|
958
|
+
.function({
|
|
959
|
+
input: [z.record(z.string(), z.unknown())],
|
|
960
|
+
output: z.promise(z.void()),
|
|
961
|
+
})
|
|
962
|
+
.optional(),
|
|
963
|
+
/**
|
|
964
|
+
* Custom merge function for config apply. Receives the existing config
|
|
965
|
+
* and the patch, returns the merged result. Optional — if omitted,
|
|
966
|
+
* the default deep-merge (object-recursive, array-replacing) is used.
|
|
967
|
+
*
|
|
968
|
+
* Use this to implement domain-specific merge strategies such as
|
|
969
|
+
* name-based array merging for inference rules.
|
|
970
|
+
*/
|
|
971
|
+
customMerge: z
|
|
972
|
+
.function({
|
|
973
|
+
input: [
|
|
974
|
+
z.record(z.string(), z.unknown()),
|
|
975
|
+
z.record(z.string(), z.unknown()),
|
|
976
|
+
],
|
|
977
|
+
output: z.record(z.string(), z.unknown()),
|
|
978
|
+
})
|
|
979
|
+
.optional(),
|
|
980
|
+
/**
|
|
981
|
+
* Returns command + args for launching the service process.
|
|
982
|
+
* Consumed by `service install`.
|
|
983
|
+
*/
|
|
984
|
+
startCommand: z.function({
|
|
985
|
+
input: [z.string()],
|
|
986
|
+
output: z.array(z.string()),
|
|
987
|
+
}),
|
|
988
|
+
/** In-process service entry point for the CLI `start` command. */
|
|
989
|
+
run: z.function({
|
|
990
|
+
input: [z.string()],
|
|
991
|
+
output: z.promise(z.void()),
|
|
992
|
+
}),
|
|
993
|
+
/** TOOLS.md section name (e.g., 'Watcher'). */
|
|
994
|
+
sectionId: z.string().min(1, 'sectionId must be a non-empty string'),
|
|
995
|
+
/** Refresh interval in seconds (must be a prime number). */
|
|
996
|
+
refreshIntervalSeconds: z.number().int().positive().refine(isPrime, {
|
|
997
|
+
message: 'refreshIntervalSeconds must be a prime number',
|
|
998
|
+
}),
|
|
999
|
+
/** Produce the component's TOOLS.md section content. */
|
|
1000
|
+
generateToolsContent: z.function({ input: [], output: z.string() }),
|
|
1001
|
+
/** Component dependencies for HEARTBEAT alert suppression. */
|
|
1002
|
+
dependencies: z
|
|
1003
|
+
.object({
|
|
1004
|
+
/** Components that must be healthy for this component to function. */
|
|
1005
|
+
hard: z.array(z.string()),
|
|
1006
|
+
/** Components that improve behavior but are not strictly required. */
|
|
1007
|
+
soft: z.array(z.string()),
|
|
1008
|
+
})
|
|
1009
|
+
.optional(),
|
|
1010
|
+
/** Extension point: add custom CLI commands to the service CLI. */
|
|
1011
|
+
customCliCommands: z
|
|
1012
|
+
.function({ input: [z.custom()], output: z.void() })
|
|
1013
|
+
.optional(),
|
|
1014
|
+
/** Extension point: return additional plugin tool descriptors. */
|
|
1015
|
+
customPluginTools: z
|
|
1016
|
+
.function({ input: [z.custom()], output: z.array(z.unknown()) })
|
|
1017
|
+
.optional(),
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Resolve the package root directory from a module's `import.meta.url`.
|
|
1022
|
+
*
|
|
1023
|
+
* @module
|
|
1024
|
+
*/
|
|
1025
|
+
/**
|
|
1026
|
+
* Get the nearest package root directory relative to the calling module URL.
|
|
1027
|
+
*
|
|
1028
|
+
* @param importMetaUrl - The `import.meta.url` of the calling module.
|
|
1029
|
+
* @returns The absolute package root path, or `undefined` on any error.
|
|
1030
|
+
*/
|
|
1031
|
+
function getPackageRoot(importMetaUrl) {
|
|
1032
|
+
try {
|
|
1033
|
+
return packageDirectorySync({ cwd: fileURLToPath(importMetaUrl) });
|
|
1034
|
+
}
|
|
1035
|
+
catch {
|
|
1036
|
+
return undefined;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
846
1040
|
/**
|
|
847
1041
|
* OpenClaw configuration helpers for plugin CLI installers.
|
|
848
1042
|
*
|
|
@@ -919,16 +1113,18 @@ function patchAllowList(parent, key, label, pluginId, mode) {
|
|
|
919
1113
|
* Patch an OpenClaw config for plugin install or uninstall.
|
|
920
1114
|
*
|
|
921
1115
|
* @remarks
|
|
922
|
-
* Manages `plugins.entries.{pluginId}
|
|
1116
|
+
* Manages `plugins.entries.{pluginId}`, `plugins.installs.{pluginId}`,
|
|
1117
|
+
* and `tools.alsoAllow`.
|
|
923
1118
|
* Idempotent: adding twice produces no duplicates; removing when absent
|
|
924
1119
|
* produces no errors.
|
|
925
1120
|
*
|
|
926
1121
|
* @param config - The parsed OpenClaw config object (mutated in place).
|
|
927
1122
|
* @param pluginId - The plugin identifier.
|
|
928
1123
|
* @param mode - Whether to add or remove the plugin.
|
|
1124
|
+
* @param installRecord - Install provenance record (required when mode is 'add').
|
|
929
1125
|
* @returns Array of log messages describing changes made.
|
|
930
1126
|
*/
|
|
931
|
-
function patchConfig(config, pluginId, mode) {
|
|
1127
|
+
function patchConfig(config, pluginId, mode, installRecord) {
|
|
932
1128
|
const messages = [];
|
|
933
1129
|
// Ensure plugins section
|
|
934
1130
|
if (!config.plugins || typeof config.plugins !== 'object') {
|
|
@@ -950,6 +1146,24 @@ function patchConfig(config, pluginId, mode) {
|
|
|
950
1146
|
Reflect.deleteProperty(entries, pluginId);
|
|
951
1147
|
messages.push(`Removed "${pluginId}" from plugins.entries`);
|
|
952
1148
|
}
|
|
1149
|
+
// plugins.installs
|
|
1150
|
+
if (!plugins.installs || typeof plugins.installs !== 'object') {
|
|
1151
|
+
plugins.installs = {};
|
|
1152
|
+
}
|
|
1153
|
+
const installs = plugins.installs;
|
|
1154
|
+
if (mode === 'add' && installRecord) {
|
|
1155
|
+
installs[pluginId] = {
|
|
1156
|
+
source: 'path',
|
|
1157
|
+
installPath: installRecord.installPath,
|
|
1158
|
+
version: installRecord.version,
|
|
1159
|
+
installedAt: installRecord.installedAt ?? new Date().toISOString(),
|
|
1160
|
+
};
|
|
1161
|
+
messages.push(`Wrote install record for "${pluginId}" to plugins.installs`);
|
|
1162
|
+
}
|
|
1163
|
+
else if (mode === 'remove' && pluginId in installs) {
|
|
1164
|
+
Reflect.deleteProperty(installs, pluginId);
|
|
1165
|
+
messages.push(`Removed install record for "${pluginId}" from plugins.installs`);
|
|
1166
|
+
}
|
|
953
1167
|
// tools.alsoAllow
|
|
954
1168
|
if (!config.tools || typeof config.tools !== 'object') {
|
|
955
1169
|
config.tools = {};
|
|
@@ -1026,8 +1240,13 @@ function readJsonFile(filePath) {
|
|
|
1026
1240
|
* @returns A Commander program ready for `.parse()`.
|
|
1027
1241
|
*/
|
|
1028
1242
|
function createPluginCli(options) {
|
|
1029
|
-
const { pluginId,
|
|
1243
|
+
const { pluginId, importMetaUrl, pluginPackage, configRoot = 'j:/config', } = options;
|
|
1030
1244
|
const componentName = options.componentName ?? deriveComponentName(pluginId);
|
|
1245
|
+
const pkgRoot = getPackageRoot(importMetaUrl);
|
|
1246
|
+
if (!pkgRoot) {
|
|
1247
|
+
throw new Error(`Unable to resolve package root for plugin CLI: ${pluginPackage}`);
|
|
1248
|
+
}
|
|
1249
|
+
const distDir = join(pkgRoot, 'dist');
|
|
1031
1250
|
const program = new Command()
|
|
1032
1251
|
.name(pluginPackage)
|
|
1033
1252
|
.description(`Jeeves ${componentName} plugin installer`);
|
|
@@ -1042,23 +1261,38 @@ function createPluginCli(options) {
|
|
|
1042
1261
|
const configPath = resolveConfigPath(openClawHome);
|
|
1043
1262
|
// 1. Copy dist to extensions
|
|
1044
1263
|
const extensionsDir = join(openClawHome, 'extensions', pluginId);
|
|
1264
|
+
if (!existsSync(distDir)) {
|
|
1265
|
+
throw new Error(`Plugin dist directory not found: ${distDir}. Ensure the plugin is built before installing.`);
|
|
1266
|
+
}
|
|
1045
1267
|
console.log(`Copying dist to ${extensionsDir}...`);
|
|
1046
1268
|
copyDistFiles(distDir, extensionsDir);
|
|
1047
1269
|
// Copy package.json and openclaw.plugin.json from package root
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
if (existsSync(src)) {
|
|
1053
|
-
copyFileSync(src, join(extensionsDir, file));
|
|
1054
|
-
}
|
|
1270
|
+
for (const file of ['package.json', 'openclaw.plugin.json']) {
|
|
1271
|
+
const src = join(pkgRoot, file);
|
|
1272
|
+
if (existsSync(src)) {
|
|
1273
|
+
copyFileSync(src, join(extensionsDir, file));
|
|
1055
1274
|
}
|
|
1056
1275
|
}
|
|
1057
1276
|
console.log(' ✓ Dist files copied');
|
|
1058
1277
|
// 2. Patch openclaw.json
|
|
1059
1278
|
console.log('Patching OpenClaw config...');
|
|
1060
1279
|
const config = readJsonFile(configPath);
|
|
1061
|
-
const
|
|
1280
|
+
const pkgJsonPathForVersion = join(extensionsDir, 'package.json');
|
|
1281
|
+
let pluginVersionForRecord;
|
|
1282
|
+
try {
|
|
1283
|
+
const pkgJsonForRecord = readJsonFile(pkgJsonPathForVersion);
|
|
1284
|
+
pluginVersionForRecord =
|
|
1285
|
+
typeof pkgJsonForRecord.version === 'string'
|
|
1286
|
+
? pkgJsonForRecord.version
|
|
1287
|
+
: undefined;
|
|
1288
|
+
}
|
|
1289
|
+
catch {
|
|
1290
|
+
// best-effort: version may not be available yet
|
|
1291
|
+
}
|
|
1292
|
+
const messages = patchConfig(config, pluginId, 'add', {
|
|
1293
|
+
installPath: extensionsDir,
|
|
1294
|
+
version: pluginVersionForRecord,
|
|
1295
|
+
});
|
|
1062
1296
|
// 3. Memory slot claim
|
|
1063
1297
|
if (opts.memory) {
|
|
1064
1298
|
if (!config.agents || typeof config.agents !== 'object') {
|
|
@@ -696,6 +696,21 @@ function createServiceManager(descriptor) {
|
|
|
696
696
|
}
|
|
697
697
|
}
|
|
698
698
|
|
|
699
|
+
/**
|
|
700
|
+
* Shared internal utility functions.
|
|
701
|
+
*
|
|
702
|
+
* @packageDocumentation
|
|
703
|
+
*/
|
|
704
|
+
/**
|
|
705
|
+
* Extract a human-readable message from an unknown caught value.
|
|
706
|
+
*
|
|
707
|
+
* @param err - The caught value (typically `unknown`).
|
|
708
|
+
* @returns The error message string.
|
|
709
|
+
*/
|
|
710
|
+
function getErrorMessage(err) {
|
|
711
|
+
return err instanceof Error ? err.message : String(err);
|
|
712
|
+
}
|
|
713
|
+
|
|
699
714
|
/**
|
|
700
715
|
* Workspace-level shared configuration: `jeeves.config.json`.
|
|
701
716
|
*
|
|
@@ -747,7 +762,13 @@ z.object({
|
|
|
747
762
|
/** Memory hygiene shared defaults. */
|
|
748
763
|
memory: workspaceMemoryConfigSchema.optional(),
|
|
749
764
|
});
|
|
750
|
-
/**
|
|
765
|
+
/**
|
|
766
|
+
* Built-in workspace config defaults.
|
|
767
|
+
*
|
|
768
|
+
* @remarks
|
|
769
|
+
* These defaults are used as the lowest-priority tier in config resolution
|
|
770
|
+
* (below CLI flags, env vars, and `jeeves.config.json` values).
|
|
771
|
+
*/
|
|
751
772
|
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
752
773
|
core: {
|
|
753
774
|
workspace: '.',
|
|
@@ -773,6 +794,10 @@ const DEFAULT_CONFIG_ROOT = WORKSPACE_CONFIG_DEFAULTS.core.configRoot;
|
|
|
773
794
|
* a component descriptor. Components add domain-specific commands
|
|
774
795
|
* via `descriptor.customCliCommands`.
|
|
775
796
|
*/
|
|
797
|
+
function handleCommandError(action, err) {
|
|
798
|
+
console.error(`${action} failed: ${getErrorMessage(err)}`);
|
|
799
|
+
process.exitCode = 1;
|
|
800
|
+
}
|
|
776
801
|
/**
|
|
777
802
|
* Create a standard service CLI program from a component descriptor.
|
|
778
803
|
*
|
|
@@ -825,8 +850,7 @@ function createServiceCli(descriptor) {
|
|
|
825
850
|
console.log(JSON.stringify(result, null, 2));
|
|
826
851
|
}
|
|
827
852
|
catch (err) {
|
|
828
|
-
|
|
829
|
-
console.error(`Service unreachable: ${msg}`);
|
|
853
|
+
console.error(`Service unreachable: ${getErrorMessage(err)}`);
|
|
830
854
|
process.exitCode = 1;
|
|
831
855
|
}
|
|
832
856
|
});
|
|
@@ -844,8 +868,7 @@ function createServiceCli(descriptor) {
|
|
|
844
868
|
console.log(JSON.stringify(result, null, 2));
|
|
845
869
|
}
|
|
846
870
|
catch (err) {
|
|
847
|
-
|
|
848
|
-
console.error(`Config query failed: ${msg}`);
|
|
871
|
+
console.error(`Config query failed: ${getErrorMessage(err)}`);
|
|
849
872
|
process.exitCode = 1;
|
|
850
873
|
}
|
|
851
874
|
});
|
|
@@ -861,8 +884,7 @@ function createServiceCli(descriptor) {
|
|
|
861
884
|
console.log('Config is valid.');
|
|
862
885
|
}
|
|
863
886
|
catch (err) {
|
|
864
|
-
|
|
865
|
-
console.error(`Validation failed: ${msg}`);
|
|
887
|
+
console.error(`Validation failed: ${getErrorMessage(err)}`);
|
|
866
888
|
process.exitCode = 1;
|
|
867
889
|
}
|
|
868
890
|
});
|
|
@@ -899,8 +921,7 @@ function createServiceCli(descriptor) {
|
|
|
899
921
|
console.log(JSON.stringify(result, null, 2));
|
|
900
922
|
}
|
|
901
923
|
catch (err) {
|
|
902
|
-
|
|
903
|
-
console.error(`Config apply failed: ${msg}`);
|
|
924
|
+
console.error(`Config apply failed: ${getErrorMessage(err)}`);
|
|
904
925
|
process.exitCode = 1;
|
|
905
926
|
}
|
|
906
927
|
});
|
|
@@ -937,9 +958,7 @@ function createServiceCli(descriptor) {
|
|
|
937
958
|
console.log(`Service "${opts.name}" installed.`);
|
|
938
959
|
}
|
|
939
960
|
catch (err) {
|
|
940
|
-
|
|
941
|
-
console.error(`Install failed: ${msg}`);
|
|
942
|
-
process.exitCode = 1;
|
|
961
|
+
handleCommandError('Install', err);
|
|
943
962
|
}
|
|
944
963
|
});
|
|
945
964
|
serviceCmd
|
|
@@ -952,9 +971,7 @@ function createServiceCli(descriptor) {
|
|
|
952
971
|
console.log(`Service "${opts.name}" uninstalled.`);
|
|
953
972
|
}
|
|
954
973
|
catch (err) {
|
|
955
|
-
|
|
956
|
-
console.error(`Uninstall failed: ${msg}`);
|
|
957
|
-
process.exitCode = 1;
|
|
974
|
+
handleCommandError('Uninstall', err);
|
|
958
975
|
}
|
|
959
976
|
});
|
|
960
977
|
serviceCmd
|
|
@@ -967,9 +984,7 @@ function createServiceCli(descriptor) {
|
|
|
967
984
|
console.log(`Service "${opts.name}" started.`);
|
|
968
985
|
}
|
|
969
986
|
catch (err) {
|
|
970
|
-
|
|
971
|
-
console.error(`Start failed: ${msg}`);
|
|
972
|
-
process.exitCode = 1;
|
|
987
|
+
handleCommandError('Start', err);
|
|
973
988
|
}
|
|
974
989
|
});
|
|
975
990
|
serviceCmd
|
|
@@ -982,9 +997,7 @@ function createServiceCli(descriptor) {
|
|
|
982
997
|
console.log(`Service "${opts.name}" stopped.`);
|
|
983
998
|
}
|
|
984
999
|
catch (err) {
|
|
985
|
-
|
|
986
|
-
console.error(`Stop failed: ${msg}`);
|
|
987
|
-
process.exitCode = 1;
|
|
1000
|
+
handleCommandError('Stop', err);
|
|
988
1001
|
}
|
|
989
1002
|
});
|
|
990
1003
|
serviceCmd
|
|
@@ -997,9 +1010,7 @@ function createServiceCli(descriptor) {
|
|
|
997
1010
|
console.log(`Service "${opts.name}" restarted.`);
|
|
998
1011
|
}
|
|
999
1012
|
catch (err) {
|
|
1000
|
-
|
|
1001
|
-
console.error(`Restart failed: ${msg}`);
|
|
1002
|
-
process.exitCode = 1;
|
|
1013
|
+
handleCommandError('Restart', err);
|
|
1003
1014
|
}
|
|
1004
1015
|
});
|
|
1005
1016
|
serviceCmd
|
|
@@ -1012,9 +1023,7 @@ function createServiceCli(descriptor) {
|
|
|
1012
1023
|
console.log(`Service "${opts.name}": ${state}`);
|
|
1013
1024
|
}
|
|
1014
1025
|
catch (err) {
|
|
1015
|
-
|
|
1016
|
-
console.error(`Status failed: ${msg}`);
|
|
1017
|
-
process.exitCode = 1;
|
|
1026
|
+
handleCommandError('Status', err);
|
|
1018
1027
|
}
|
|
1019
1028
|
});
|
|
1020
1029
|
// Apply custom CLI commands if provided
|