@karmaniverous/jeeves-meta 0.12.4 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli/jeeves-meta/index.js +390 -262
- package/dist/index.d.ts +5 -6
- package/dist/index.js +335 -205
- package/package.json +10 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs, { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, statSync, readdirSync, watchFile } from 'node:fs';
|
|
3
|
-
import path, { join, dirname, relative, posix, resolve } from 'node:path';
|
|
3
|
+
import path, { join, dirname, basename, relative, posix, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
4
5
|
import require$$0$4 from 'path';
|
|
5
6
|
import require$$0$3 from 'fs';
|
|
6
7
|
import require$$0$1 from 'constants';
|
|
@@ -9,13 +10,12 @@ import require$$4 from 'util';
|
|
|
9
10
|
import require$$5 from 'assert';
|
|
10
11
|
import require$$2 from 'events';
|
|
11
12
|
import vm from 'vm';
|
|
13
|
+
import { z } from 'zod';
|
|
12
14
|
import * as commander from 'commander';
|
|
13
15
|
import { homedir, tmpdir } from 'node:os';
|
|
14
|
-
import { z } from 'zod';
|
|
15
16
|
import { execSync } from 'node:child_process';
|
|
16
17
|
import { fileURLToPath } from 'node:url';
|
|
17
18
|
import { readFile, unlink, mkdir, writeFile, copyFile } from 'node:fs/promises';
|
|
18
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
19
19
|
import pino from 'pino';
|
|
20
20
|
import Handlebars from 'handlebars';
|
|
21
21
|
import { Cron } from 'croner';
|
|
@@ -7017,6 +7017,32 @@ const COMPONENT_CONFIG_PREFIX = 'jeeves-';
|
|
|
7017
7017
|
/** Core config file name. */
|
|
7018
7018
|
const CONFIG_FILE = 'config.json';
|
|
7019
7019
|
|
|
7020
|
+
/**
|
|
7021
|
+
* Default port assignments for Jeeves platform services.
|
|
7022
|
+
*
|
|
7023
|
+
* @remarks
|
|
7024
|
+
* Each port number is a historical reference:
|
|
7025
|
+
* - 1934: Wodehouse's *Thank You, Jeeves*; Popper's *Logic of Scientific Discovery*
|
|
7026
|
+
* - 1936: Turing's "On Computable Numbers"; Church's lambda calculus
|
|
7027
|
+
* - 1937: Turing's paper in *Proceedings of the London Mathematical Society*
|
|
7028
|
+
* - 1938: Wodehouse's *The Code of the Woosters*; Shannon's relay/switching paper
|
|
7029
|
+
*/
|
|
7030
|
+
/** Default port for jeeves-server. */
|
|
7031
|
+
const SERVER_PORT = 1934;
|
|
7032
|
+
/** Default port for jeeves-watcher. */
|
|
7033
|
+
const WATCHER_PORT = 1936;
|
|
7034
|
+
/** Default port for jeeves-runner. */
|
|
7035
|
+
const RUNNER_PORT = 1937;
|
|
7036
|
+
/** Default port for jeeves-meta. */
|
|
7037
|
+
const META_PORT = 1938;
|
|
7038
|
+
/** Map of service names to their default ports. */
|
|
7039
|
+
const DEFAULT_PORTS = {
|
|
7040
|
+
server: SERVER_PORT,
|
|
7041
|
+
watcher: WATCHER_PORT,
|
|
7042
|
+
runner: RUNNER_PORT,
|
|
7043
|
+
meta: META_PORT,
|
|
7044
|
+
};
|
|
7045
|
+
|
|
7020
7046
|
/**
|
|
7021
7047
|
* Workspace and config root initialization.
|
|
7022
7048
|
*
|
|
@@ -7062,30 +7088,64 @@ function getComponentConfigDir(componentName) {
|
|
|
7062
7088
|
throw new Error('jeeves-core: init() must be called first');
|
|
7063
7089
|
return join(state.configRoot, `${COMPONENT_CONFIG_PREFIX}${componentName}`);
|
|
7064
7090
|
}
|
|
7091
|
+
/** Maximum rename retry attempts on EPERM. */
|
|
7092
|
+
const ATOMIC_WRITE_MAX_RETRIES = 3;
|
|
7093
|
+
/** Delay between EPERM retries in milliseconds. */
|
|
7094
|
+
const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
|
|
7065
7095
|
/**
|
|
7066
7096
|
* Write content to a file atomically via a temp file + rename.
|
|
7067
7097
|
*
|
|
7098
|
+
* @remarks
|
|
7099
|
+
* Retries the rename up to three times on EPERM (Windows file-handle
|
|
7100
|
+
* contention) with a 100 ms synchronous delay between attempts.
|
|
7101
|
+
*
|
|
7068
7102
|
* @param filePath - Absolute path to the target file.
|
|
7069
7103
|
* @param content - Content to write.
|
|
7070
7104
|
*/
|
|
7071
7105
|
function atomicWrite(filePath, content) {
|
|
7072
7106
|
const dir = dirname(filePath);
|
|
7073
|
-
const
|
|
7107
|
+
const base = basename(filePath, '.md');
|
|
7108
|
+
const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
|
|
7074
7109
|
writeFileSync(tempPath, content, 'utf-8');
|
|
7075
|
-
|
|
7076
|
-
renameSync(tempPath, filePath);
|
|
7077
|
-
}
|
|
7078
|
-
catch (err) {
|
|
7110
|
+
for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
|
|
7079
7111
|
try {
|
|
7080
|
-
|
|
7112
|
+
renameSync(tempPath, filePath);
|
|
7113
|
+
return;
|
|
7081
7114
|
}
|
|
7082
|
-
catch {
|
|
7083
|
-
|
|
7115
|
+
catch (err) {
|
|
7116
|
+
const isEperm = err instanceof Error &&
|
|
7117
|
+
'code' in err &&
|
|
7118
|
+
err.code === 'EPERM';
|
|
7119
|
+
if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
|
|
7120
|
+
try {
|
|
7121
|
+
unlinkSync(tempPath);
|
|
7122
|
+
}
|
|
7123
|
+
catch {
|
|
7124
|
+
/* best-effort cleanup */
|
|
7125
|
+
}
|
|
7126
|
+
throw err;
|
|
7127
|
+
}
|
|
7128
|
+
// Synchronous sleep before retry (acceptable in atomic write context)
|
|
7129
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
|
|
7084
7130
|
}
|
|
7085
|
-
throw err;
|
|
7086
7131
|
}
|
|
7087
7132
|
}
|
|
7088
7133
|
|
|
7134
|
+
/**
|
|
7135
|
+
* Shared internal utility functions.
|
|
7136
|
+
*
|
|
7137
|
+
* @packageDocumentation
|
|
7138
|
+
*/
|
|
7139
|
+
/**
|
|
7140
|
+
* Extract a human-readable message from an unknown caught value.
|
|
7141
|
+
*
|
|
7142
|
+
* @param err - The caught value (typically `unknown`).
|
|
7143
|
+
* @returns The error message string.
|
|
7144
|
+
*/
|
|
7145
|
+
function getErrorMessage(err) {
|
|
7146
|
+
return err instanceof Error ? err.message : String(err);
|
|
7147
|
+
}
|
|
7148
|
+
|
|
7089
7149
|
/**
|
|
7090
7150
|
* Factory for a framework-agnostic config apply HTTP handler.
|
|
7091
7151
|
*
|
|
@@ -7138,8 +7198,7 @@ function readConfigFile(filePath) {
|
|
|
7138
7198
|
return JSON.parse(raw);
|
|
7139
7199
|
}
|
|
7140
7200
|
catch (err) {
|
|
7141
|
-
|
|
7142
|
-
console.warn(`jeeves-core: Could not read config file ${filePath}: ${msg}`);
|
|
7201
|
+
console.warn(`jeeves-core: Could not read config file ${filePath}: ${getErrorMessage(err)}`);
|
|
7143
7202
|
return {};
|
|
7144
7203
|
}
|
|
7145
7204
|
}
|
|
@@ -7188,10 +7247,9 @@ function createConfigApplyHandler(descriptor) {
|
|
|
7188
7247
|
atomicWrite(configPath, json);
|
|
7189
7248
|
}
|
|
7190
7249
|
catch (err) {
|
|
7191
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7192
7250
|
return {
|
|
7193
7251
|
status: 500,
|
|
7194
|
-
body: { error: `Failed to write config: ${
|
|
7252
|
+
body: { error: `Failed to write config: ${getErrorMessage(err)}` },
|
|
7195
7253
|
};
|
|
7196
7254
|
}
|
|
7197
7255
|
// Call onConfigApply callback if defined
|
|
@@ -7200,12 +7258,11 @@ function createConfigApplyHandler(descriptor) {
|
|
|
7200
7258
|
await descriptor.onConfigApply(validatedConfig);
|
|
7201
7259
|
}
|
|
7202
7260
|
catch (err) {
|
|
7203
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7204
7261
|
return {
|
|
7205
7262
|
status: 200,
|
|
7206
7263
|
body: {
|
|
7207
7264
|
applied: true,
|
|
7208
|
-
warning: `Config written but callback failed: ${
|
|
7265
|
+
warning: `Config written but callback failed: ${getErrorMessage(err)}`,
|
|
7209
7266
|
config: validatedConfig,
|
|
7210
7267
|
},
|
|
7211
7268
|
};
|
|
@@ -7288,8 +7345,7 @@ function createStatusHandler(options) {
|
|
|
7288
7345
|
health = await options.getHealth();
|
|
7289
7346
|
}
|
|
7290
7347
|
catch (err) {
|
|
7291
|
-
|
|
7292
|
-
health = { error: message };
|
|
7348
|
+
health = { error: getErrorMessage(err) };
|
|
7293
7349
|
overallStatus = 'degraded';
|
|
7294
7350
|
}
|
|
7295
7351
|
}
|
|
@@ -7305,6 +7361,70 @@ function createStatusHandler(options) {
|
|
|
7305
7361
|
};
|
|
7306
7362
|
};
|
|
7307
7363
|
}
|
|
7364
|
+
/** Core shared config section. */
|
|
7365
|
+
const workspaceCoreConfigSchema = z
|
|
7366
|
+
.object({
|
|
7367
|
+
/** Workspace root path. */
|
|
7368
|
+
workspace: z.string().optional().describe('Workspace root path'),
|
|
7369
|
+
/** Platform config root path. */
|
|
7370
|
+
configRoot: z.string().optional().describe('Platform config root path'),
|
|
7371
|
+
/** OpenClaw gateway URL. */
|
|
7372
|
+
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
7373
|
+
})
|
|
7374
|
+
.partial();
|
|
7375
|
+
/** Memory shared config section. */
|
|
7376
|
+
const workspaceMemoryConfigSchema = z
|
|
7377
|
+
.object({
|
|
7378
|
+
/** MEMORY.md character budget. */
|
|
7379
|
+
budget: z.number().int().positive().optional().describe('Memory budget'),
|
|
7380
|
+
/** Warning threshold as a fraction of budget. */
|
|
7381
|
+
warningThreshold: z
|
|
7382
|
+
.number()
|
|
7383
|
+
.min(0)
|
|
7384
|
+
.max(1)
|
|
7385
|
+
.optional()
|
|
7386
|
+
.describe('Memory warning threshold'),
|
|
7387
|
+
/** Staleness threshold in days. */
|
|
7388
|
+
staleDays: z
|
|
7389
|
+
.number()
|
|
7390
|
+
.int()
|
|
7391
|
+
.positive()
|
|
7392
|
+
.optional()
|
|
7393
|
+
.describe('Memory staleness threshold in days'),
|
|
7394
|
+
})
|
|
7395
|
+
.partial();
|
|
7396
|
+
/** Workspace config Zod schema. */
|
|
7397
|
+
z.object({
|
|
7398
|
+
/** JSON Schema pointer for IDE autocomplete. */
|
|
7399
|
+
$schema: z.string().optional().describe('JSON Schema pointer'),
|
|
7400
|
+
/** Core shared defaults. */
|
|
7401
|
+
core: workspaceCoreConfigSchema.optional(),
|
|
7402
|
+
/** Memory hygiene shared defaults. */
|
|
7403
|
+
memory: workspaceMemoryConfigSchema.optional(),
|
|
7404
|
+
});
|
|
7405
|
+
/**
|
|
7406
|
+
* Built-in workspace config defaults.
|
|
7407
|
+
*
|
|
7408
|
+
* @remarks
|
|
7409
|
+
* These defaults are used as the lowest-priority tier in config resolution
|
|
7410
|
+
* (below CLI flags, env vars, and `jeeves.config.json` values).
|
|
7411
|
+
*/
|
|
7412
|
+
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
7413
|
+
core: {
|
|
7414
|
+
workspace: '.',
|
|
7415
|
+
configRoot: './config'}};
|
|
7416
|
+
|
|
7417
|
+
/**
|
|
7418
|
+
* Shared CLI defaults and resolution for Jeeves CLI commands.
|
|
7419
|
+
*
|
|
7420
|
+
* @remarks
|
|
7421
|
+
* All root CLI commands share workspace/config-root resolution. Values follow
|
|
7422
|
+
* the shared precedence model: flags → env → jeeves.config.json → defaults.
|
|
7423
|
+
*/
|
|
7424
|
+
/** Default workspace path. */
|
|
7425
|
+
const DEFAULT_WORKSPACE = WORKSPACE_CONFIG_DEFAULTS.core.workspace;
|
|
7426
|
+
/** Default config root path. */
|
|
7427
|
+
const DEFAULT_CONFIG_ROOT = WORKSPACE_CONFIG_DEFAULTS.core.configRoot;
|
|
7308
7428
|
|
|
7309
7429
|
function getDefaultExportFromCjs (x) {
|
|
7310
7430
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -7690,7 +7810,7 @@ function isExecError(err) {
|
|
|
7690
7810
|
* (Windows), systemd (Linux), or launchd (macOS) based on platform.
|
|
7691
7811
|
*/
|
|
7692
7812
|
/** Exec helper that returns stdout. */
|
|
7693
|
-
function run(cmd) {
|
|
7813
|
+
function run$1(cmd) {
|
|
7694
7814
|
return execSync(cmd, {
|
|
7695
7815
|
encoding: 'utf-8',
|
|
7696
7816
|
timeout: 30_000,
|
|
@@ -7743,31 +7863,31 @@ function createWindowsManager(descriptor) {
|
|
|
7743
7863
|
const cmdArgs = descriptor.startCommand(cfgPath);
|
|
7744
7864
|
const appPath = cmdArgs[0];
|
|
7745
7865
|
const appArgs = cmdArgs.slice(1).join(' ');
|
|
7746
|
-
run(`nssm install ${svcName} ${appPath}`);
|
|
7866
|
+
run$1(`nssm install ${svcName} ${appPath}`);
|
|
7747
7867
|
if (appArgs) {
|
|
7748
|
-
run(`nssm set ${svcName} AppParameters ${appArgs}`);
|
|
7868
|
+
run$1(`nssm set ${svcName} AppParameters ${appArgs}`);
|
|
7749
7869
|
}
|
|
7750
|
-
run(`nssm set ${svcName} AppStdout ${join(homedir(), `${svcName}.log`)}`);
|
|
7751
|
-
run(`nssm set ${svcName} AppStderr ${join(homedir(), `${svcName}.log`)}`);
|
|
7752
|
-
run(`nssm set ${svcName} AppRotateFiles 1`);
|
|
7753
|
-
run(`nssm set ${svcName} AppRotateBytes 1048576`);
|
|
7870
|
+
run$1(`nssm set ${svcName} AppStdout ${join(homedir(), `${svcName}.log`)}`);
|
|
7871
|
+
run$1(`nssm set ${svcName} AppStderr ${join(homedir(), `${svcName}.log`)}`);
|
|
7872
|
+
run$1(`nssm set ${svcName} AppRotateFiles 1`);
|
|
7873
|
+
run$1(`nssm set ${svcName} AppRotateBytes 1048576`);
|
|
7754
7874
|
},
|
|
7755
7875
|
uninstall(options) {
|
|
7756
7876
|
const svcName = resolveServiceName(descriptor, options);
|
|
7757
7877
|
runQuiet(`nssm stop ${svcName}`);
|
|
7758
|
-
run(`nssm remove ${svcName} confirm`);
|
|
7878
|
+
run$1(`nssm remove ${svcName} confirm`);
|
|
7759
7879
|
},
|
|
7760
7880
|
start(options) {
|
|
7761
7881
|
const svcName = resolveServiceName(descriptor, options);
|
|
7762
|
-
run(`nssm start ${svcName}`);
|
|
7882
|
+
run$1(`nssm start ${svcName}`);
|
|
7763
7883
|
},
|
|
7764
7884
|
stop(options) {
|
|
7765
7885
|
const svcName = resolveServiceName(descriptor, options);
|
|
7766
|
-
run(`nssm stop ${svcName}`);
|
|
7886
|
+
run$1(`nssm stop ${svcName}`);
|
|
7767
7887
|
},
|
|
7768
7888
|
restart(options) {
|
|
7769
7889
|
const svcName = resolveServiceName(descriptor, options);
|
|
7770
|
-
run(`nssm restart ${svcName}`);
|
|
7890
|
+
run$1(`nssm restart ${svcName}`);
|
|
7771
7891
|
},
|
|
7772
7892
|
status(options) {
|
|
7773
7893
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7812,8 +7932,8 @@ function createLinuxManager(descriptor) {
|
|
|
7812
7932
|
const cmdArgs = descriptor.startCommand(cfgPath);
|
|
7813
7933
|
mkdirSync(unitDir, { recursive: true });
|
|
7814
7934
|
writeFileSync(unitPath(svcName), buildSystemdUnit(svcName, cmdArgs));
|
|
7815
|
-
run('systemctl --user daemon-reload');
|
|
7816
|
-
run(`systemctl --user enable ${svcName}.service`);
|
|
7935
|
+
run$1('systemctl --user daemon-reload');
|
|
7936
|
+
run$1(`systemctl --user enable ${svcName}.service`);
|
|
7817
7937
|
},
|
|
7818
7938
|
uninstall(options) {
|
|
7819
7939
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7826,15 +7946,15 @@ function createLinuxManager(descriptor) {
|
|
|
7826
7946
|
},
|
|
7827
7947
|
start(options) {
|
|
7828
7948
|
const svcName = resolveServiceName(descriptor, options);
|
|
7829
|
-
run(`systemctl --user start ${svcName}.service`);
|
|
7949
|
+
run$1(`systemctl --user start ${svcName}.service`);
|
|
7830
7950
|
},
|
|
7831
7951
|
stop(options) {
|
|
7832
7952
|
const svcName = resolveServiceName(descriptor, options);
|
|
7833
|
-
run(`systemctl --user stop ${svcName}.service`);
|
|
7953
|
+
run$1(`systemctl --user stop ${svcName}.service`);
|
|
7834
7954
|
},
|
|
7835
7955
|
restart(options) {
|
|
7836
7956
|
const svcName = resolveServiceName(descriptor, options);
|
|
7837
|
-
run(`systemctl --user restart ${svcName}.service`);
|
|
7957
|
+
run$1(`systemctl --user restart ${svcName}.service`);
|
|
7838
7958
|
},
|
|
7839
7959
|
status(options) {
|
|
7840
7960
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7898,16 +8018,16 @@ function createMacOSManager(descriptor) {
|
|
|
7898
8018
|
},
|
|
7899
8019
|
start(options) {
|
|
7900
8020
|
const svcName = resolveServiceName(descriptor, options);
|
|
7901
|
-
run(`launchctl load ${plistPath(svcName)}`);
|
|
8021
|
+
run$1(`launchctl load ${plistPath(svcName)}`);
|
|
7902
8022
|
},
|
|
7903
8023
|
stop(options) {
|
|
7904
8024
|
const svcName = resolveServiceName(descriptor, options);
|
|
7905
|
-
run(`launchctl unload ${plistPath(svcName)}`);
|
|
8025
|
+
run$1(`launchctl unload ${plistPath(svcName)}`);
|
|
7906
8026
|
},
|
|
7907
8027
|
restart(options) {
|
|
7908
8028
|
const svcName = resolveServiceName(descriptor, options);
|
|
7909
8029
|
runQuiet(`launchctl unload ${plistPath(svcName)}`);
|
|
7910
|
-
run(`launchctl load ${plistPath(svcName)}`);
|
|
8030
|
+
run$1(`launchctl load ${plistPath(svcName)}`);
|
|
7911
8031
|
},
|
|
7912
8032
|
status(options) {
|
|
7913
8033
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7936,19 +8056,6 @@ function createServiceManager(descriptor) {
|
|
|
7936
8056
|
}
|
|
7937
8057
|
}
|
|
7938
8058
|
|
|
7939
|
-
/**
|
|
7940
|
-
* Shared CLI defaults and option registration for Jeeves CLI commands.
|
|
7941
|
-
*
|
|
7942
|
-
* @remarks
|
|
7943
|
-
* All three CLI commands (install, uninstall, status) share the same
|
|
7944
|
-
* `--workspace` and `--config-root` options with the same defaults.
|
|
7945
|
-
* This module centralizes them to eliminate duplication.
|
|
7946
|
-
*/
|
|
7947
|
-
/** Default workspace path (current directory). */
|
|
7948
|
-
const DEFAULT_WORKSPACE = '.';
|
|
7949
|
-
/** Default config root path. */
|
|
7950
|
-
const DEFAULT_CONFIG_ROOT = './config';
|
|
7951
|
-
|
|
7952
8059
|
/**
|
|
7953
8060
|
* Factory for the standard Jeeves service CLI.
|
|
7954
8061
|
*
|
|
@@ -7957,6 +8064,10 @@ const DEFAULT_CONFIG_ROOT = './config';
|
|
|
7957
8064
|
* a component descriptor. Components add domain-specific commands
|
|
7958
8065
|
* via `descriptor.customCliCommands`.
|
|
7959
8066
|
*/
|
|
8067
|
+
function handleCommandError(action, err) {
|
|
8068
|
+
console.error(`${action} failed: ${getErrorMessage(err)}`);
|
|
8069
|
+
process.exitCode = 1;
|
|
8070
|
+
}
|
|
7960
8071
|
/**
|
|
7961
8072
|
* Create a standard service CLI program from a component descriptor.
|
|
7962
8073
|
*
|
|
@@ -8009,8 +8120,7 @@ function createServiceCli(descriptor) {
|
|
|
8009
8120
|
console.log(JSON.stringify(result, null, 2));
|
|
8010
8121
|
}
|
|
8011
8122
|
catch (err) {
|
|
8012
|
-
|
|
8013
|
-
console.error(`Service unreachable: ${msg}`);
|
|
8123
|
+
console.error(`Service unreachable: ${getErrorMessage(err)}`);
|
|
8014
8124
|
process.exitCode = 1;
|
|
8015
8125
|
}
|
|
8016
8126
|
});
|
|
@@ -8028,8 +8138,7 @@ function createServiceCli(descriptor) {
|
|
|
8028
8138
|
console.log(JSON.stringify(result, null, 2));
|
|
8029
8139
|
}
|
|
8030
8140
|
catch (err) {
|
|
8031
|
-
|
|
8032
|
-
console.error(`Config query failed: ${msg}`);
|
|
8141
|
+
console.error(`Config query failed: ${getErrorMessage(err)}`);
|
|
8033
8142
|
process.exitCode = 1;
|
|
8034
8143
|
}
|
|
8035
8144
|
});
|
|
@@ -8045,8 +8154,7 @@ function createServiceCli(descriptor) {
|
|
|
8045
8154
|
console.log('Config is valid.');
|
|
8046
8155
|
}
|
|
8047
8156
|
catch (err) {
|
|
8048
|
-
|
|
8049
|
-
console.error(`Validation failed: ${msg}`);
|
|
8157
|
+
console.error(`Validation failed: ${getErrorMessage(err)}`);
|
|
8050
8158
|
process.exitCode = 1;
|
|
8051
8159
|
}
|
|
8052
8160
|
});
|
|
@@ -8083,8 +8191,7 @@ function createServiceCli(descriptor) {
|
|
|
8083
8191
|
console.log(JSON.stringify(result, null, 2));
|
|
8084
8192
|
}
|
|
8085
8193
|
catch (err) {
|
|
8086
|
-
|
|
8087
|
-
console.error(`Config apply failed: ${msg}`);
|
|
8194
|
+
console.error(`Config apply failed: ${getErrorMessage(err)}`);
|
|
8088
8195
|
process.exitCode = 1;
|
|
8089
8196
|
}
|
|
8090
8197
|
});
|
|
@@ -8121,9 +8228,7 @@ function createServiceCli(descriptor) {
|
|
|
8121
8228
|
console.log(`Service "${opts.name}" installed.`);
|
|
8122
8229
|
}
|
|
8123
8230
|
catch (err) {
|
|
8124
|
-
|
|
8125
|
-
console.error(`Install failed: ${msg}`);
|
|
8126
|
-
process.exitCode = 1;
|
|
8231
|
+
handleCommandError('Install', err);
|
|
8127
8232
|
}
|
|
8128
8233
|
});
|
|
8129
8234
|
serviceCmd
|
|
@@ -8136,9 +8241,7 @@ function createServiceCli(descriptor) {
|
|
|
8136
8241
|
console.log(`Service "${opts.name}" uninstalled.`);
|
|
8137
8242
|
}
|
|
8138
8243
|
catch (err) {
|
|
8139
|
-
|
|
8140
|
-
console.error(`Uninstall failed: ${msg}`);
|
|
8141
|
-
process.exitCode = 1;
|
|
8244
|
+
handleCommandError('Uninstall', err);
|
|
8142
8245
|
}
|
|
8143
8246
|
});
|
|
8144
8247
|
serviceCmd
|
|
@@ -8151,9 +8254,7 @@ function createServiceCli(descriptor) {
|
|
|
8151
8254
|
console.log(`Service "${opts.name}" started.`);
|
|
8152
8255
|
}
|
|
8153
8256
|
catch (err) {
|
|
8154
|
-
|
|
8155
|
-
console.error(`Start failed: ${msg}`);
|
|
8156
|
-
process.exitCode = 1;
|
|
8257
|
+
handleCommandError('Start', err);
|
|
8157
8258
|
}
|
|
8158
8259
|
});
|
|
8159
8260
|
serviceCmd
|
|
@@ -8166,9 +8267,7 @@ function createServiceCli(descriptor) {
|
|
|
8166
8267
|
console.log(`Service "${opts.name}" stopped.`);
|
|
8167
8268
|
}
|
|
8168
8269
|
catch (err) {
|
|
8169
|
-
|
|
8170
|
-
console.error(`Stop failed: ${msg}`);
|
|
8171
|
-
process.exitCode = 1;
|
|
8270
|
+
handleCommandError('Stop', err);
|
|
8172
8271
|
}
|
|
8173
8272
|
});
|
|
8174
8273
|
serviceCmd
|
|
@@ -8181,9 +8280,7 @@ function createServiceCli(descriptor) {
|
|
|
8181
8280
|
console.log(`Service "${opts.name}" restarted.`);
|
|
8182
8281
|
}
|
|
8183
8282
|
catch (err) {
|
|
8184
|
-
|
|
8185
|
-
console.error(`Restart failed: ${msg}`);
|
|
8186
|
-
process.exitCode = 1;
|
|
8283
|
+
handleCommandError('Restart', err);
|
|
8187
8284
|
}
|
|
8188
8285
|
});
|
|
8189
8286
|
serviceCmd
|
|
@@ -8196,9 +8293,7 @@ function createServiceCli(descriptor) {
|
|
|
8196
8293
|
console.log(`Service "${opts.name}": ${state}`);
|
|
8197
8294
|
}
|
|
8198
8295
|
catch (err) {
|
|
8199
|
-
|
|
8200
|
-
console.error(`Status failed: ${msg}`);
|
|
8201
|
-
process.exitCode = 1;
|
|
8296
|
+
handleCommandError('Status', err);
|
|
8202
8297
|
}
|
|
8203
8298
|
});
|
|
8204
8299
|
// Apply custom CLI commands if provided
|
|
@@ -8283,6 +8378,38 @@ function loadConfig(configDir) {
|
|
|
8283
8378
|
}
|
|
8284
8379
|
}
|
|
8285
8380
|
|
|
8381
|
+
/**
|
|
8382
|
+
* Service URL resolution.
|
|
8383
|
+
*
|
|
8384
|
+
* @remarks
|
|
8385
|
+
* Resolves the URL for a named Jeeves service using the following
|
|
8386
|
+
* resolution order:
|
|
8387
|
+
* 1. Consumer's own component config
|
|
8388
|
+
* 2. Core config (`{configRoot}/jeeves-core/config.json`)
|
|
8389
|
+
* 3. Default port constants
|
|
8390
|
+
*/
|
|
8391
|
+
/**
|
|
8392
|
+
* Resolve the URL for a named Jeeves service.
|
|
8393
|
+
*
|
|
8394
|
+
* @param serviceName - The service name (e.g., 'watcher', 'runner').
|
|
8395
|
+
* @param consumerName - Optional consumer component name for config override.
|
|
8396
|
+
* @returns The resolved service URL.
|
|
8397
|
+
* @throws Error if `init()` has not been called or the service is unknown.
|
|
8398
|
+
*/
|
|
8399
|
+
function getServiceUrl(serviceName, consumerName) {
|
|
8400
|
+
// 2. Check core config
|
|
8401
|
+
const coreDir = getCoreConfigDir();
|
|
8402
|
+
const coreConfig = loadConfig(coreDir);
|
|
8403
|
+
const coreUrl = coreConfig?.services[serviceName]?.url;
|
|
8404
|
+
if (coreUrl)
|
|
8405
|
+
return coreUrl;
|
|
8406
|
+
// 3. Fall back to port constants
|
|
8407
|
+
const port = DEFAULT_PORTS[serviceName];
|
|
8408
|
+
{
|
|
8409
|
+
return `http://127.0.0.1:${String(port)}`;
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
|
|
8286
8413
|
/**
|
|
8287
8414
|
* Resolve the bind address for a Jeeves service.
|
|
8288
8415
|
*
|
|
@@ -8320,6 +8447,10 @@ function getBindAddress(componentName) {
|
|
|
8320
8447
|
// Tier 4: Default
|
|
8321
8448
|
return DEFAULT_BIND_ADDRESS;
|
|
8322
8449
|
}
|
|
8450
|
+
/** Async sleep via setTimeout. */
|
|
8451
|
+
function sleepAsync(ms) {
|
|
8452
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
8453
|
+
}
|
|
8323
8454
|
|
|
8324
8455
|
/**
|
|
8325
8456
|
* Shared live config hot-reload support.
|
|
@@ -8337,7 +8468,6 @@ function getBindAddress(componentName) {
|
|
|
8337
8468
|
*/
|
|
8338
8469
|
const RESTART_REQUIRED_FIELDS = [
|
|
8339
8470
|
'port',
|
|
8340
|
-
'host',
|
|
8341
8471
|
'watcherUrl',
|
|
8342
8472
|
'gatewayUrl',
|
|
8343
8473
|
'gatewayApiKey',
|
|
@@ -8449,8 +8579,6 @@ const autoSeedRuleSchema = z.object({
|
|
|
8449
8579
|
const serviceConfigSchema = metaConfigSchema.extend({
|
|
8450
8580
|
/** HTTP port for the service (default: 1938). */
|
|
8451
8581
|
port: z.number().int().min(1).max(65535).default(1938),
|
|
8452
|
-
/** Bind address for the HTTP server (default: 127.0.0.1). */
|
|
8453
|
-
host: z.string().default('127.0.0.1'),
|
|
8454
8582
|
/** Cron schedule for synthesis cycles (default: every 30 min). */
|
|
8455
8583
|
schedule: z.string().default('*/30 * * * *'),
|
|
8456
8584
|
/** Optional channel identifier for reporting. */
|
|
@@ -8810,6 +8938,174 @@ async function readMetaJson(metaPath) {
|
|
|
8810
8938
|
return JSON.parse(raw);
|
|
8811
8939
|
}
|
|
8812
8940
|
|
|
8941
|
+
/**
|
|
8942
|
+
* Escape special glob characters in a path so it can be used as a literal
|
|
8943
|
+
* prefix in glob patterns.
|
|
8944
|
+
*
|
|
8945
|
+
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8946
|
+
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8947
|
+
* special characters are matched literally by the watcher's walk endpoint.
|
|
8948
|
+
*
|
|
8949
|
+
* @module escapeGlob
|
|
8950
|
+
*/
|
|
8951
|
+
/**
|
|
8952
|
+
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8953
|
+
*
|
|
8954
|
+
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8955
|
+
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8956
|
+
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8957
|
+
*
|
|
8958
|
+
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8959
|
+
*
|
|
8960
|
+
* @param s - Raw path string.
|
|
8961
|
+
* @returns String with glob metacharacters wrapped in character classes.
|
|
8962
|
+
*/
|
|
8963
|
+
function escapeGlob(s) {
|
|
8964
|
+
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8965
|
+
}
|
|
8966
|
+
|
|
8967
|
+
/**
|
|
8968
|
+
* Filter file paths by modification time.
|
|
8969
|
+
*
|
|
8970
|
+
* Shared utility for staleness detection and delta file enumeration.
|
|
8971
|
+
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8972
|
+
*
|
|
8973
|
+
* @module mtimeFilter
|
|
8974
|
+
*/
|
|
8975
|
+
/**
|
|
8976
|
+
* Check if any file in the list was modified after the given timestamp.
|
|
8977
|
+
*
|
|
8978
|
+
* Short-circuits on first match for efficiency (staleness checks).
|
|
8979
|
+
*
|
|
8980
|
+
* @param files - Array of file paths to check.
|
|
8981
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
8982
|
+
* @returns True if any file was modified after the timestamp.
|
|
8983
|
+
*/
|
|
8984
|
+
function hasModifiedAfter(files, afterMs) {
|
|
8985
|
+
for (const filePath of files) {
|
|
8986
|
+
try {
|
|
8987
|
+
if (statSync(filePath).mtimeMs > afterMs)
|
|
8988
|
+
return true;
|
|
8989
|
+
}
|
|
8990
|
+
catch {
|
|
8991
|
+
// Unreadable file — skip
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
return false;
|
|
8995
|
+
}
|
|
8996
|
+
/**
|
|
8997
|
+
* Filter files to only those modified after the given timestamp.
|
|
8998
|
+
*
|
|
8999
|
+
* @param files - Array of file paths to filter.
|
|
9000
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
9001
|
+
* @returns Filtered array of file paths.
|
|
9002
|
+
*/
|
|
9003
|
+
function filterModifiedAfter(files, afterMs) {
|
|
9004
|
+
return files.filter((filePath) => {
|
|
9005
|
+
try {
|
|
9006
|
+
return statSync(filePath).mtimeMs > afterMs;
|
|
9007
|
+
}
|
|
9008
|
+
catch {
|
|
9009
|
+
return false;
|
|
9010
|
+
}
|
|
9011
|
+
});
|
|
9012
|
+
}
|
|
9013
|
+
|
|
9014
|
+
/**
|
|
9015
|
+
* Staleness detection via watcher walk.
|
|
9016
|
+
*
|
|
9017
|
+
* A meta is stale when any watched file in its scope was modified after
|
|
9018
|
+
* `_generatedAt`.
|
|
9019
|
+
*
|
|
9020
|
+
* @module scheduling/staleness
|
|
9021
|
+
*/
|
|
9022
|
+
/**
|
|
9023
|
+
* Check if a meta is stale.
|
|
9024
|
+
*
|
|
9025
|
+
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
9026
|
+
* then applies a local mtime check (fast) to detect any modifications since
|
|
9027
|
+
* `_generatedAt`. Short-circuits on first match.
|
|
9028
|
+
*
|
|
9029
|
+
* @param scopePrefix - Path prefix for this meta's scope.
|
|
9030
|
+
* @param meta - Current meta.json content.
|
|
9031
|
+
* @param watcher - WatcherClient instance.
|
|
9032
|
+
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
9033
|
+
*/
|
|
9034
|
+
async function isStale(scopePrefix, meta, watcher) {
|
|
9035
|
+
if (!meta._generatedAt)
|
|
9036
|
+
return true; // Never synthesized = stale
|
|
9037
|
+
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
9038
|
+
// Exclude .meta/ subtree — synthesis outputs must not trigger staleness.
|
|
9039
|
+
// Handle both forward and back slashes for cross-platform compatibility.
|
|
9040
|
+
const metaSep = /[/\\]\.meta(?:[/\\]|$)/;
|
|
9041
|
+
const filtered = files.filter((f) => !metaSep.test(f));
|
|
9042
|
+
return hasModifiedAfter(filtered, new Date(meta._generatedAt).getTime());
|
|
9043
|
+
}
|
|
9044
|
+
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
9045
|
+
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
9046
|
+
/**
|
|
9047
|
+
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
9048
|
+
*
|
|
9049
|
+
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
9050
|
+
* (1 year) so that depth weighting can differentiate them. Without
|
|
9051
|
+
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
9052
|
+
*
|
|
9053
|
+
* @param meta - Current meta.json content.
|
|
9054
|
+
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
9055
|
+
*/
|
|
9056
|
+
function actualStaleness(meta) {
|
|
9057
|
+
if (!meta._generatedAt)
|
|
9058
|
+
return MAX_STALENESS_SECONDS;
|
|
9059
|
+
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
9060
|
+
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
9061
|
+
}
|
|
9062
|
+
/**
|
|
9063
|
+
* Check whether the architect step should be triggered.
|
|
9064
|
+
*
|
|
9065
|
+
* @param meta - Current meta.json.
|
|
9066
|
+
* @param structureChanged - Whether the structure hash changed.
|
|
9067
|
+
* @param steerChanged - Whether the steer directive changed.
|
|
9068
|
+
* @param architectEvery - Config: run architect every N cycles.
|
|
9069
|
+
* @returns True if the architect step should run.
|
|
9070
|
+
*/
|
|
9071
|
+
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
9072
|
+
return (!meta._builder ||
|
|
9073
|
+
structureChanged ||
|
|
9074
|
+
steerChanged ||
|
|
9075
|
+
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
9076
|
+
}
|
|
9077
|
+
/**
|
|
9078
|
+
* Detect whether the steer directive changed since the last archive.
|
|
9079
|
+
*
|
|
9080
|
+
* @param currentSteer - Current _steer value (or undefined).
|
|
9081
|
+
* @param archiveSteer - Archive _steer value (or undefined).
|
|
9082
|
+
* @param hasArchive - Whether an archive snapshot exists.
|
|
9083
|
+
* @returns True if steer changed.
|
|
9084
|
+
*/
|
|
9085
|
+
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
9086
|
+
if (!hasArchive)
|
|
9087
|
+
return Boolean(currentSteer);
|
|
9088
|
+
return currentSteer !== archiveSteer;
|
|
9089
|
+
}
|
|
9090
|
+
/**
|
|
9091
|
+
* Compute a normalized staleness score (0–1) for display purposes.
|
|
9092
|
+
*
|
|
9093
|
+
* Uses the same depth/emphasis weighting as candidate selection,
|
|
9094
|
+
* normalized to a 30-day window.
|
|
9095
|
+
*
|
|
9096
|
+
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
9097
|
+
* @param depth - Meta tree depth.
|
|
9098
|
+
* @param emphasis - Scheduling emphasis multiplier.
|
|
9099
|
+
* @param depthWeight - Depth weighting exponent from config.
|
|
9100
|
+
* @returns Normalized score between 0 and 1.
|
|
9101
|
+
*/
|
|
9102
|
+
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
9103
|
+
if (stalenessSeconds === null)
|
|
9104
|
+
return 1;
|
|
9105
|
+
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
9106
|
+
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
9107
|
+
}
|
|
9108
|
+
|
|
8813
9109
|
/**
|
|
8814
9110
|
* Build the ownership tree from discovered .meta/ paths.
|
|
8815
9111
|
*
|
|
@@ -8892,8 +9188,6 @@ function findNode(tree, targetPath) {
|
|
|
8892
9188
|
*
|
|
8893
9189
|
* @module discovery/listMetas
|
|
8894
9190
|
*/
|
|
8895
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
8896
|
-
const MAX_STALENESS_SECONDS$1 = 365 * 86_400;
|
|
8897
9191
|
/**
|
|
8898
9192
|
* Discover, deduplicate, and enrich all metas.
|
|
8899
9193
|
*
|
|
@@ -8929,7 +9223,7 @@ async function listMetas(config, watcher) {
|
|
|
8929
9223
|
// Compute staleness
|
|
8930
9224
|
let stalenessSeconds;
|
|
8931
9225
|
if (neverSynth) {
|
|
8932
|
-
stalenessSeconds = MAX_STALENESS_SECONDS
|
|
9226
|
+
stalenessSeconds = MAX_STALENESS_SECONDS;
|
|
8933
9227
|
}
|
|
8934
9228
|
else {
|
|
8935
9229
|
const genAt = new Date(meta._generatedAt).getTime();
|
|
@@ -8962,79 +9256,6 @@ async function listMetas(config, watcher) {
|
|
|
8962
9256
|
};
|
|
8963
9257
|
}
|
|
8964
9258
|
|
|
8965
|
-
/**
|
|
8966
|
-
* Escape special glob characters in a path so it can be used as a literal
|
|
8967
|
-
* prefix in glob patterns.
|
|
8968
|
-
*
|
|
8969
|
-
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8970
|
-
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8971
|
-
* special characters are matched literally by the watcher's walk endpoint.
|
|
8972
|
-
*
|
|
8973
|
-
* @module escapeGlob
|
|
8974
|
-
*/
|
|
8975
|
-
/**
|
|
8976
|
-
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8977
|
-
*
|
|
8978
|
-
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8979
|
-
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8980
|
-
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8981
|
-
*
|
|
8982
|
-
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8983
|
-
*
|
|
8984
|
-
* @param s - Raw path string.
|
|
8985
|
-
* @returns String with glob metacharacters wrapped in character classes.
|
|
8986
|
-
*/
|
|
8987
|
-
function escapeGlob(s) {
|
|
8988
|
-
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8989
|
-
}
|
|
8990
|
-
|
|
8991
|
-
/**
|
|
8992
|
-
* Filter file paths by modification time.
|
|
8993
|
-
*
|
|
8994
|
-
* Shared utility for staleness detection and delta file enumeration.
|
|
8995
|
-
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8996
|
-
*
|
|
8997
|
-
* @module mtimeFilter
|
|
8998
|
-
*/
|
|
8999
|
-
/**
|
|
9000
|
-
* Check if any file in the list was modified after the given timestamp.
|
|
9001
|
-
*
|
|
9002
|
-
* Short-circuits on first match for efficiency (staleness checks).
|
|
9003
|
-
*
|
|
9004
|
-
* @param files - Array of file paths to check.
|
|
9005
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
9006
|
-
* @returns True if any file was modified after the timestamp.
|
|
9007
|
-
*/
|
|
9008
|
-
function hasModifiedAfter(files, afterMs) {
|
|
9009
|
-
for (const filePath of files) {
|
|
9010
|
-
try {
|
|
9011
|
-
if (statSync(filePath).mtimeMs > afterMs)
|
|
9012
|
-
return true;
|
|
9013
|
-
}
|
|
9014
|
-
catch {
|
|
9015
|
-
// Unreadable file — skip
|
|
9016
|
-
}
|
|
9017
|
-
}
|
|
9018
|
-
return false;
|
|
9019
|
-
}
|
|
9020
|
-
/**
|
|
9021
|
-
* Filter files to only those modified after the given timestamp.
|
|
9022
|
-
*
|
|
9023
|
-
* @param files - Array of file paths to filter.
|
|
9024
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
9025
|
-
* @returns Filtered array of file paths.
|
|
9026
|
-
*/
|
|
9027
|
-
function filterModifiedAfter(files, afterMs) {
|
|
9028
|
-
return files.filter((filePath) => {
|
|
9029
|
-
try {
|
|
9030
|
-
return statSync(filePath).mtimeMs > afterMs;
|
|
9031
|
-
}
|
|
9032
|
-
catch {
|
|
9033
|
-
return false;
|
|
9034
|
-
}
|
|
9035
|
-
});
|
|
9036
|
-
}
|
|
9037
|
-
|
|
9038
9259
|
/**
|
|
9039
9260
|
* Compute the file scope owned by a meta node.
|
|
9040
9261
|
*
|
|
@@ -9111,11 +9332,6 @@ function getDeltaFiles(generatedAt, scopeFiles) {
|
|
|
9111
9332
|
return filterModifiedAfter(scopeFiles, new Date(generatedAt).getTime());
|
|
9112
9333
|
}
|
|
9113
9334
|
|
|
9114
|
-
/** Sleep for a given number of milliseconds. */
|
|
9115
|
-
function sleep(ms) {
|
|
9116
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9117
|
-
}
|
|
9118
|
-
|
|
9119
9335
|
/**
|
|
9120
9336
|
* Error thrown when a spawned subprocess is aborted via AbortController.
|
|
9121
9337
|
*
|
|
@@ -9270,7 +9486,7 @@ class GatewayExecutor {
|
|
|
9270
9486
|
JSON.stringify(spawnResult));
|
|
9271
9487
|
}
|
|
9272
9488
|
// Step 2: Poll for completion via sessions_history
|
|
9273
|
-
await
|
|
9489
|
+
await sleepAsync(3000);
|
|
9274
9490
|
while (Date.now() < deadline) {
|
|
9275
9491
|
// Check for abort before each poll iteration
|
|
9276
9492
|
if (this.controller.signal.aborted) {
|
|
@@ -9334,7 +9550,7 @@ class GatewayExecutor {
|
|
|
9334
9550
|
catch {
|
|
9335
9551
|
// Transient poll failure — keep trying
|
|
9336
9552
|
}
|
|
9337
|
-
await
|
|
9553
|
+
await sleepAsync(this.pollIntervalMs);
|
|
9338
9554
|
}
|
|
9339
9555
|
throw new SpawnTimeoutError('Synthesis subprocess timed out after ' + timeoutMs.toString() + 'ms', outputPath);
|
|
9340
9556
|
}
|
|
@@ -10195,101 +10411,6 @@ function discoverStalestPath(candidates, depthWeight) {
|
|
|
10195
10411
|
return winner?.node.metaPath ?? null;
|
|
10196
10412
|
}
|
|
10197
10413
|
|
|
10198
|
-
/**
|
|
10199
|
-
* Staleness detection via watcher walk.
|
|
10200
|
-
*
|
|
10201
|
-
* A meta is stale when any watched file in its scope was modified after
|
|
10202
|
-
* `_generatedAt`.
|
|
10203
|
-
*
|
|
10204
|
-
* @module scheduling/staleness
|
|
10205
|
-
*/
|
|
10206
|
-
/**
|
|
10207
|
-
* Check if a meta is stale.
|
|
10208
|
-
*
|
|
10209
|
-
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
10210
|
-
* then applies a local mtime check (fast) to detect any modifications since
|
|
10211
|
-
* `_generatedAt`. Short-circuits on first match.
|
|
10212
|
-
*
|
|
10213
|
-
* @param scopePrefix - Path prefix for this meta's scope.
|
|
10214
|
-
* @param meta - Current meta.json content.
|
|
10215
|
-
* @param watcher - WatcherClient instance.
|
|
10216
|
-
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
10217
|
-
*/
|
|
10218
|
-
async function isStale(scopePrefix, meta, watcher) {
|
|
10219
|
-
if (!meta._generatedAt)
|
|
10220
|
-
return true; // Never synthesized = stale
|
|
10221
|
-
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
10222
|
-
// Exclude .meta/ subtree — synthesis outputs must not trigger staleness.
|
|
10223
|
-
// Handle both forward and back slashes for cross-platform compatibility.
|
|
10224
|
-
const metaSep = /[/\\]\.meta(?:[/\\]|$)/;
|
|
10225
|
-
const filtered = files.filter((f) => !metaSep.test(f));
|
|
10226
|
-
return hasModifiedAfter(filtered, new Date(meta._generatedAt).getTime());
|
|
10227
|
-
}
|
|
10228
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
10229
|
-
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
10230
|
-
/**
|
|
10231
|
-
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
10232
|
-
*
|
|
10233
|
-
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
10234
|
-
* (1 year) so that depth weighting can differentiate them. Without
|
|
10235
|
-
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
10236
|
-
*
|
|
10237
|
-
* @param meta - Current meta.json content.
|
|
10238
|
-
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
10239
|
-
*/
|
|
10240
|
-
function actualStaleness(meta) {
|
|
10241
|
-
if (!meta._generatedAt)
|
|
10242
|
-
return MAX_STALENESS_SECONDS;
|
|
10243
|
-
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
10244
|
-
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
10245
|
-
}
|
|
10246
|
-
/**
|
|
10247
|
-
* Check whether the architect step should be triggered.
|
|
10248
|
-
*
|
|
10249
|
-
* @param meta - Current meta.json.
|
|
10250
|
-
* @param structureChanged - Whether the structure hash changed.
|
|
10251
|
-
* @param steerChanged - Whether the steer directive changed.
|
|
10252
|
-
* @param architectEvery - Config: run architect every N cycles.
|
|
10253
|
-
* @returns True if the architect step should run.
|
|
10254
|
-
*/
|
|
10255
|
-
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
10256
|
-
return (!meta._builder ||
|
|
10257
|
-
structureChanged ||
|
|
10258
|
-
steerChanged ||
|
|
10259
|
-
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
10260
|
-
}
|
|
10261
|
-
/**
|
|
10262
|
-
* Detect whether the steer directive changed since the last archive.
|
|
10263
|
-
*
|
|
10264
|
-
* @param currentSteer - Current _steer value (or undefined).
|
|
10265
|
-
* @param archiveSteer - Archive _steer value (or undefined).
|
|
10266
|
-
* @param hasArchive - Whether an archive snapshot exists.
|
|
10267
|
-
* @returns True if steer changed.
|
|
10268
|
-
*/
|
|
10269
|
-
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
10270
|
-
if (!hasArchive)
|
|
10271
|
-
return Boolean(currentSteer);
|
|
10272
|
-
return currentSteer !== archiveSteer;
|
|
10273
|
-
}
|
|
10274
|
-
/**
|
|
10275
|
-
* Compute a normalized staleness score (0–1) for display purposes.
|
|
10276
|
-
*
|
|
10277
|
-
* Uses the same depth/emphasis weighting as candidate selection,
|
|
10278
|
-
* normalized to a 30-day window.
|
|
10279
|
-
*
|
|
10280
|
-
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
10281
|
-
* @param depth - Meta tree depth.
|
|
10282
|
-
* @param emphasis - Scheduling emphasis multiplier.
|
|
10283
|
-
* @param depthWeight - Depth weighting exponent from config.
|
|
10284
|
-
* @returns Normalized score between 0 and 1.
|
|
10285
|
-
*/
|
|
10286
|
-
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
10287
|
-
if (stalenessSeconds === null)
|
|
10288
|
-
return 1;
|
|
10289
|
-
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
10290
|
-
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
10291
|
-
}
|
|
10292
|
-
|
|
10293
10414
|
/**
|
|
10294
10415
|
* Shared error utilities.
|
|
10295
10416
|
*
|
|
@@ -11350,7 +11471,10 @@ function buildMetaRules(config) {
|
|
|
11350
11471
|
properties: {
|
|
11351
11472
|
file: {
|
|
11352
11473
|
properties: {
|
|
11353
|
-
path: {
|
|
11474
|
+
path: {
|
|
11475
|
+
type: 'string',
|
|
11476
|
+
glob: '**/jeeves-meta{.config.json,/config.json}',
|
|
11477
|
+
},
|
|
11354
11478
|
},
|
|
11355
11479
|
},
|
|
11356
11480
|
},
|
|
@@ -12511,7 +12635,7 @@ class HttpWatcherClient {
|
|
|
12511
12635
|
}
|
|
12512
12636
|
// Exponential backoff
|
|
12513
12637
|
const delayMs = this.backoffBaseMs * Math.pow(this.backoffFactor, attempt);
|
|
12514
|
-
await
|
|
12638
|
+
await sleepAsync(delayMs);
|
|
12515
12639
|
}
|
|
12516
12640
|
// Unreachable, but TypeScript needs it
|
|
12517
12641
|
throw new Error('Retry exhausted');
|
|
@@ -12730,7 +12854,11 @@ async function startService(config, configPath) {
|
|
|
12730
12854
|
*/
|
|
12731
12855
|
/** Build the full API URL for a given port string and path. */
|
|
12732
12856
|
function apiUrl(port, apiPath) {
|
|
12733
|
-
|
|
12857
|
+
const url = new URL(apiPath, getServiceUrl('meta'));
|
|
12858
|
+
if (port !== DEFAULT_PORT_STR) {
|
|
12859
|
+
url.port = port;
|
|
12860
|
+
}
|
|
12861
|
+
return url.toString();
|
|
12734
12862
|
}
|
|
12735
12863
|
/** Wrap an async CLI action with consistent error handling. */
|
|
12736
12864
|
function withErrorHandling(fn, label) {
|