@karmaniverous/jeeves-meta 0.18.0 → 0.19.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/cli/jeeves-meta/index.js +140 -52
- package/package.json +14 -12
|
@@ -5,7 +5,7 @@ import fs, { unlinkSync, writeFileSync, existsSync, readFileSync, statSync, mkdi
|
|
|
5
5
|
import path, { join, dirname, relative, posix, sep, resolve } from 'node:path';
|
|
6
6
|
import { readFile, unlink, mkdir, writeFile, copyFile } from 'node:fs/promises';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
8
|
+
import { randomUUID, createHash, createHmac } from 'node:crypto';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import pino from 'pino';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
@@ -763,11 +763,14 @@ function applyHotReloadedConfig(newConfig) {
|
|
|
763
763
|
*
|
|
764
764
|
* @module schema/config
|
|
765
765
|
*/
|
|
766
|
-
/**
|
|
766
|
+
/**
|
|
767
|
+
* Default Handlebars template strings for progress reporting. Single source of truth.
|
|
768
|
+
* Links are empty when jeeves-server refuses `serverKey`, so each is guarded.
|
|
769
|
+
*/
|
|
767
770
|
const DEFAULT_TEMPLATE_STRINGS = {
|
|
768
|
-
phaseStart: ':gear: Started meta synthesis {{phase}} phase of <{{dirLink}}>',
|
|
769
|
-
phaseEnd: ':white_check_mark: Completed meta synthesis {{phase}} phase ({{tokens}} tokens / {{seconds}}s) at <{{metaLink}}>',
|
|
770
|
-
phaseError: ':x: Meta synthesis {{phase}} phase failed at <{{dirLink}}
|
|
771
|
+
phaseStart: ':gear: Started meta synthesis {{phase}} phase{{#if dirLink}} of <{{dirLink}}>{{/if}}',
|
|
772
|
+
phaseEnd: ':white_check_mark: Completed meta synthesis {{phase}} phase ({{tokens}} tokens / {{seconds}}s){{#if metaLink}} at <{{metaLink}}>{{/if}}',
|
|
773
|
+
phaseError: ':x: Meta synthesis {{phase}} phase failed{{#if dirLink}} at <{{dirLink}}>{{/if}}\n Error: {{error}}',
|
|
771
774
|
};
|
|
772
775
|
/** Zod schema for logging configuration. */
|
|
773
776
|
const loggingSchema = z.object({
|
|
@@ -803,6 +806,13 @@ const serviceConfigSchema = metaConfigSchema.extend({
|
|
|
803
806
|
* Default: http://127.0.0.1:1934
|
|
804
807
|
*/
|
|
805
808
|
serverUrl: z.string().default('http://127.0.0.1:1934'),
|
|
809
|
+
/**
|
|
810
|
+
* jeeves-server machine key seed (a `keys` entry in jeeves-server config).
|
|
811
|
+
* The seed is never sent: the resolve-path API receives only the key
|
|
812
|
+
* derived from it, as the `k` parameter. Required from jeeves-server 3.15.0.
|
|
813
|
+
* Supports `${ENV}` substitution. Redacted in `GET /config`.
|
|
814
|
+
*/
|
|
815
|
+
serverKey: z.string().optional(),
|
|
806
816
|
/**
|
|
807
817
|
* Handlebars templates for progress reporting messages.
|
|
808
818
|
* Each template receives a standard set of data keys (dirLink, metaLink,
|
|
@@ -3018,13 +3028,82 @@ async function executePhase(node, currentMeta, phaseState, phase, config, execut
|
|
|
3018
3028
|
}
|
|
3019
3029
|
|
|
3020
3030
|
/**
|
|
3021
|
-
*
|
|
3031
|
+
* Resolve filesystem paths to jeeves-server browse links via
|
|
3032
|
+
* `GET /api/resolve-path`, authenticated with a jeeves-server machine key.
|
|
3033
|
+
* Network I/O only; no logging (callers decide how to report auth failures).
|
|
3034
|
+
*
|
|
3035
|
+
* @module progress/resolveLink
|
|
3036
|
+
*/
|
|
3037
|
+
/** Timeout for resolve-path API calls (ms). */
|
|
3038
|
+
const RESOLVE_PATH_TIMEOUT_MS = 3000;
|
|
3039
|
+
/** The fields of a resolve-path 200 response this module uses. */
|
|
3040
|
+
const resolvePathResponseSchema = z.object({
|
|
3041
|
+
browseUrl: z.string().optional(),
|
|
3042
|
+
publicUrl: z.string().optional(),
|
|
3043
|
+
});
|
|
3044
|
+
/**
|
|
3045
|
+
* Derive the URL key jeeves-server accepts for a machine key seed.
|
|
3022
3046
|
*
|
|
3023
|
-
*
|
|
3024
|
-
*
|
|
3025
|
-
*
|
|
3047
|
+
* Mirrors `computeInsiderKey` in `@karmaniverous/jeeves-server-core`:
|
|
3048
|
+
* the first 32 hex chars of HMAC-SHA256(seed, 'insider'). Kept local so this
|
|
3049
|
+
* service does not pull in a second copy of `@karmaniverous/jeeves`.
|
|
3026
3050
|
*
|
|
3027
|
-
* @
|
|
3051
|
+
* @param seed - Machine key seed, as configured under `keys` in jeeves-server.
|
|
3052
|
+
* @returns The derived URL key.
|
|
3053
|
+
*/
|
|
3054
|
+
function deriveServerUrlKey(seed) {
|
|
3055
|
+
return createHmac('sha256', seed)
|
|
3056
|
+
.update('insider')
|
|
3057
|
+
.digest('hex')
|
|
3058
|
+
.substring(0, 32);
|
|
3059
|
+
}
|
|
3060
|
+
/**
|
|
3061
|
+
* Resolve a filesystem path to a browse URL.
|
|
3062
|
+
*
|
|
3063
|
+
* Sends the derived machine key as the `k` query parameter when `serverKey`
|
|
3064
|
+
* is set. On HTTP 200 uses `publicUrl` if present, otherwise prefixes
|
|
3065
|
+
* `serverUrl` to `browseUrl`. A malformed 200 response falls back.
|
|
3066
|
+
*
|
|
3067
|
+
* @param fsPath - Absolute filesystem path to resolve.
|
|
3068
|
+
* @param serverUrl - jeeves-server base URL.
|
|
3069
|
+
* @param serverKey - Optional jeeves-server machine key seed.
|
|
3070
|
+
* @returns The lookup outcome.
|
|
3071
|
+
*/
|
|
3072
|
+
async function resolveLink(fsPath, serverUrl, serverKey) {
|
|
3073
|
+
const fallback = { kind: 'fallback', link: fsPath };
|
|
3074
|
+
try {
|
|
3075
|
+
const url = new URL('/api/resolve-path', serverUrl);
|
|
3076
|
+
url.searchParams.set('fsPath', fsPath);
|
|
3077
|
+
if (serverKey)
|
|
3078
|
+
url.searchParams.set('k', deriveServerUrlKey(serverKey));
|
|
3079
|
+
const res = await fetchWithTimeout(url.toString(), RESOLVE_PATH_TIMEOUT_MS);
|
|
3080
|
+
if (res.status === 401 || res.status === 403)
|
|
3081
|
+
return { kind: 'unauthorized', status: res.status };
|
|
3082
|
+
if (!res.ok)
|
|
3083
|
+
return fallback;
|
|
3084
|
+
const parsed = resolvePathResponseSchema.safeParse(await res.json());
|
|
3085
|
+
if (!parsed.success)
|
|
3086
|
+
return fallback;
|
|
3087
|
+
const data = parsed.data;
|
|
3088
|
+
if (data.publicUrl)
|
|
3089
|
+
return { kind: 'resolved', link: data.publicUrl };
|
|
3090
|
+
if (data.browseUrl)
|
|
3091
|
+
return {
|
|
3092
|
+
kind: 'resolved',
|
|
3093
|
+
link: serverUrl.replace(/\/+$/, '') + data.browseUrl,
|
|
3094
|
+
};
|
|
3095
|
+
return fallback;
|
|
3096
|
+
}
|
|
3097
|
+
catch {
|
|
3098
|
+
return fallback;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
/**
|
|
3103
|
+
* Render progress events with Handlebars templates, resolving owner-path
|
|
3104
|
+
* browse links through jeeves-server. Network I/O via resolveLink only.
|
|
3105
|
+
*
|
|
3106
|
+
* @module progress/render
|
|
3028
3107
|
*/
|
|
3029
3108
|
/** Compile raw template strings into Handlebars delegates. */
|
|
3030
3109
|
function compileTemplates(raw) {
|
|
@@ -3043,40 +3122,24 @@ function formatNumber(n) {
|
|
|
3043
3122
|
return n.toLocaleString('en-US');
|
|
3044
3123
|
}
|
|
3045
3124
|
/**
|
|
3046
|
-
* Resolve
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
* On HTTP 200, uses publicUrl if present, otherwise prefixes serverUrl
|
|
3050
|
-
* to browseUrl. On any error or non-200 response, returns fsPath as-is.
|
|
3125
|
+
* Resolve the path and link fields for an owner directory and its meta.json.
|
|
3126
|
+
* When the server refuses the key, every field is empty: no raw filesystem
|
|
3127
|
+
* path reaches any template, default or custom.
|
|
3051
3128
|
*/
|
|
3052
|
-
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
url.searchParams.set('fsPath', fsPath);
|
|
3058
|
-
const res = await fetchWithTimeout(url.toString(), RESOLVE_PATH_TIMEOUT_MS);
|
|
3059
|
-
if (!res.ok)
|
|
3060
|
-
return fsPath;
|
|
3061
|
-
const data = (await res.json());
|
|
3062
|
-
if (data.publicUrl)
|
|
3063
|
-
return data.publicUrl;
|
|
3064
|
-
if (data.browseUrl) {
|
|
3065
|
-
const base = serverUrl.replace(/\/+$/, '');
|
|
3066
|
-
return base + data.browseUrl;
|
|
3067
|
-
}
|
|
3068
|
-
return fsPath;
|
|
3129
|
+
async function resolvePathFields(ownerPath, serverUrl, options) {
|
|
3130
|
+
const result = await resolveLink(ownerPath, serverUrl, options.serverKey);
|
|
3131
|
+
if (result.kind === 'unauthorized') {
|
|
3132
|
+
options.onUnauthorized?.(result.status);
|
|
3133
|
+
return { dirPath: '', metaPath: '', dirLink: '', metaLink: '' };
|
|
3069
3134
|
}
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
const metaLink = dirLink.replace(/\/+$/, '') + '/.meta/meta.json';
|
|
3079
|
-
return { dirLink, metaLink };
|
|
3135
|
+
const dirLink = result.link;
|
|
3136
|
+
return {
|
|
3137
|
+
dirPath: ownerPath,
|
|
3138
|
+
metaPath: ownerPath.replace(/\/+$/, '') + '/.meta/meta.json',
|
|
3139
|
+
dirLink,
|
|
3140
|
+
// Derive metaLink by appending /.meta/meta.json to the resolved dir link
|
|
3141
|
+
metaLink: dirLink.replace(/\/+$/, '') + '/.meta/meta.json',
|
|
3142
|
+
};
|
|
3080
3143
|
}
|
|
3081
3144
|
/**
|
|
3082
3145
|
* Render the appropriate template for a progress event.
|
|
@@ -3084,18 +3147,13 @@ async function resolveLinks(ownerPath, serverUrl) {
|
|
|
3084
3147
|
* @param event - The progress event to render.
|
|
3085
3148
|
* @param templates - Compiled Handlebars templates.
|
|
3086
3149
|
* @param serverUrl - jeeves-server URL for resolve-path API.
|
|
3150
|
+
* @param options - Machine key and unauthorized-response callback.
|
|
3087
3151
|
* @returns Rendered message string.
|
|
3088
3152
|
*/
|
|
3089
|
-
async function renderProgressEvent(event, templates, serverUrl) {
|
|
3090
|
-
const ownerPath = event.path;
|
|
3091
|
-
const metaPath = ownerPath.replace(/\/+$/, '') + '/.meta/meta.json';
|
|
3153
|
+
async function renderProgressEvent(event, templates, serverUrl, options = {}) {
|
|
3092
3154
|
const phase = (event.phase ?? 'unknown').toUpperCase();
|
|
3093
|
-
const { dirLink, metaLink } = await resolveLinks(ownerPath, serverUrl);
|
|
3094
3155
|
const base = {
|
|
3095
|
-
|
|
3096
|
-
metaPath,
|
|
3097
|
-
dirLink,
|
|
3098
|
-
metaLink,
|
|
3156
|
+
...(await resolvePathFields(event.path, serverUrl, options)),
|
|
3099
3157
|
phase,
|
|
3100
3158
|
};
|
|
3101
3159
|
switch (event.type) {
|
|
@@ -3119,6 +3177,18 @@ async function renderProgressEvent(event, templates, serverUrl) {
|
|
|
3119
3177
|
return 'Unknown progress event';
|
|
3120
3178
|
}
|
|
3121
3179
|
}
|
|
3180
|
+
|
|
3181
|
+
/**
|
|
3182
|
+
* Progress reporting via OpenClaw gateway `/tools/invoke` → `message` tool.
|
|
3183
|
+
*
|
|
3184
|
+
* Progress events are rendered using Handlebars templates (see
|
|
3185
|
+
* `progress/render`). Owner-path links are resolved via the jeeves-server
|
|
3186
|
+
* resolve-path API (authenticated with `serverKey`), falling back to raw
|
|
3187
|
+
* filesystem paths when the server is unreachable and omitting links when
|
|
3188
|
+
* the server refuses the key.
|
|
3189
|
+
*
|
|
3190
|
+
* @module progress
|
|
3191
|
+
*/
|
|
3122
3192
|
class ProgressReporter {
|
|
3123
3193
|
config;
|
|
3124
3194
|
logger;
|
|
@@ -3126,6 +3196,8 @@ class ProgressReporter {
|
|
|
3126
3196
|
/** Snapshot of template source strings used to compile `this.templates`. */
|
|
3127
3197
|
lastTemplateSource;
|
|
3128
3198
|
serverUrl;
|
|
3199
|
+
/** Unauthorized causes already warned about; each is logged once. */
|
|
3200
|
+
reportedAuthCauses = new Set();
|
|
3129
3201
|
constructor(config, logger) {
|
|
3130
3202
|
this.config = config;
|
|
3131
3203
|
this.logger = logger;
|
|
@@ -3154,7 +3226,12 @@ class ProgressReporter {
|
|
|
3154
3226
|
}
|
|
3155
3227
|
let message;
|
|
3156
3228
|
try {
|
|
3157
|
-
message = await renderProgressEvent(event, this.templates, this.serverUrl
|
|
3229
|
+
message = await renderProgressEvent(event, this.templates, this.serverUrl, {
|
|
3230
|
+
serverKey: this.config.serverKey,
|
|
3231
|
+
onUnauthorized: (status) => {
|
|
3232
|
+
this.warnUnauthorized(status);
|
|
3233
|
+
},
|
|
3234
|
+
});
|
|
3158
3235
|
}
|
|
3159
3236
|
catch (err) {
|
|
3160
3237
|
this.logger.warn({ err }, 'Progress event rendering failed');
|
|
@@ -3191,6 +3268,16 @@ class ProgressReporter {
|
|
|
3191
3268
|
this.logger.warn({ err }, 'Progress reporting threw');
|
|
3192
3269
|
}
|
|
3193
3270
|
}
|
|
3271
|
+
/** Warn once per distinct cause when jeeves-server refuses resolve-path. */
|
|
3272
|
+
warnUnauthorized(status) {
|
|
3273
|
+
const reason = this.config.serverKey ? 'rejected' : 'missing';
|
|
3274
|
+
if (this.reportedAuthCauses.has(reason))
|
|
3275
|
+
return;
|
|
3276
|
+
this.reportedAuthCauses.add(reason);
|
|
3277
|
+
this.logger.warn({ status, serverUrl: this.serverUrl }, reason === 'missing'
|
|
3278
|
+
? 'jeeves-server requires a machine key for resolve-path; set serverKey in meta config. Progress reports omit links until then.'
|
|
3279
|
+
: 'jeeves-server rejected serverKey for resolve-path; check the machine key. Progress reports omit links until then.');
|
|
3280
|
+
}
|
|
3194
3281
|
}
|
|
3195
3282
|
|
|
3196
3283
|
/**
|
|
@@ -4001,11 +4088,12 @@ class Scheduler {
|
|
|
4001
4088
|
*
|
|
4002
4089
|
* @module routes/config
|
|
4003
4090
|
*/
|
|
4004
|
-
/** Return a sanitized copy of the config (redact gatewayApiKey). */
|
|
4091
|
+
/** Return a sanitized copy of the config (redact gatewayApiKey and serverKey). */
|
|
4005
4092
|
function sanitizeConfig(config) {
|
|
4006
4093
|
return {
|
|
4007
4094
|
...config,
|
|
4008
4095
|
gatewayApiKey: config.gatewayApiKey ? '[REDACTED]' : undefined,
|
|
4096
|
+
serverKey: config.serverKey ? '[REDACTED]' : undefined,
|
|
4009
4097
|
};
|
|
4010
4098
|
}
|
|
4011
4099
|
function registerConfigRoute(app, deps) {
|
package/package.json
CHANGED
|
@@ -7,31 +7,31 @@
|
|
|
7
7
|
"url": "https://github.com/karmaniverous/jeeves-meta/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@karmaniverous/jeeves": "0.
|
|
11
|
-
"@karmaniverous/jeeves-meta-core": "^0.
|
|
10
|
+
"@karmaniverous/jeeves": "0.7.0",
|
|
11
|
+
"@karmaniverous/jeeves-meta-core": "^0.3.0",
|
|
12
12
|
"commander": "^15",
|
|
13
13
|
"croner": "^10",
|
|
14
14
|
"fastify": "^5.12",
|
|
15
|
-
"handlebars": "^4.7.
|
|
15
|
+
"handlebars": "^4.7.10",
|
|
16
16
|
"package-directory": "^8.2.0",
|
|
17
17
|
"pino": "^10",
|
|
18
18
|
"zod": "^4.6"
|
|
19
19
|
},
|
|
20
20
|
"description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@dotenvx/dotenvx": "^2.
|
|
22
|
+
"@dotenvx/dotenvx": "^2.35.0",
|
|
23
23
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
24
24
|
"@rollup/plugin-json": "^6.1.0",
|
|
25
25
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
26
26
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
27
|
-
"@types/node": "^26.6.
|
|
28
|
-
"@vitest/coverage-v8": "^5.0.
|
|
27
|
+
"@types/node": "^26.6.5",
|
|
28
|
+
"@vitest/coverage-v8": "^5.0.4",
|
|
29
29
|
"cross-env": "^10.1.0",
|
|
30
|
-
"release-it": "^21.1.
|
|
31
|
-
"rollup": "^4.
|
|
30
|
+
"release-it": "^21.1.1",
|
|
31
|
+
"rollup": "^4.64.6",
|
|
32
32
|
"rollup-plugin-copy": "^3.5.0",
|
|
33
33
|
"tslib": "^2.8.1",
|
|
34
|
-
"vitest": "^5.0.
|
|
34
|
+
"vitest": "^5.0.4"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=22"
|
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"tagName": "service/${version}"
|
|
63
63
|
},
|
|
64
64
|
"github": {
|
|
65
|
-
"release": true
|
|
65
|
+
"release": true,
|
|
66
|
+
"releaseName": "${name.split('/').pop()}@${version}",
|
|
67
|
+
"releaseNotes": "npx git-cliff --tag service/${version} --unreleased --strip header"
|
|
66
68
|
},
|
|
67
69
|
"hooks": {
|
|
68
70
|
"after:bump": [
|
|
@@ -81,7 +83,7 @@
|
|
|
81
83
|
"git switch ${branchName}"
|
|
82
84
|
],
|
|
83
85
|
"before:npm:release": [
|
|
84
|
-
"npx git-cliff -o CHANGELOG.md",
|
|
86
|
+
"npx git-cliff --tag service/${version} -o CHANGELOG.md",
|
|
85
87
|
"git add -A"
|
|
86
88
|
]
|
|
87
89
|
},
|
|
@@ -106,5 +108,5 @@
|
|
|
106
108
|
"typecheck": "tsc"
|
|
107
109
|
},
|
|
108
110
|
"type": "module",
|
|
109
|
-
"version": "0.
|
|
111
|
+
"version": "0.19.0"
|
|
110
112
|
}
|