@tpsdev-ai/flair 0.29.0 → 0.30.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-shim.cjs +1 -1
- package/dist/cli.js +231 -104
- package/dist/deploy.js +29 -19
- package/dist/fabric-upgrade.js +61 -23
- package/dist/fleet-verify.js +3 -3
- package/dist/lib/mcp-enable.js +2 -2
- package/dist/rem/scheduler.js +226 -17
- package/dist/resources/A2AAdapter.js +1 -1
- package/dist/resources/Admin.js +1 -1
- package/dist/resources/AdminConnectors.js +1 -1
- package/dist/resources/AdminDashboard.js +1 -1
- package/dist/resources/AdminIdp.js +1 -1
- package/dist/resources/AdminInstance.js +1 -1
- package/dist/resources/AdminMemory.js +1 -1
- package/dist/resources/AdminPrincipals.js +1 -1
- package/dist/resources/Agent.js +1 -1
- package/dist/resources/AgentCard.js +1 -1
- package/dist/resources/AgentSeed.js +1 -1
- package/dist/resources/AttentionQuery.js +1 -1
- package/dist/resources/Credential.js +1 -1
- package/dist/resources/Federation.js +1 -1
- package/dist/resources/Instance.js +1 -1
- package/dist/resources/Integration.js +1 -1
- package/dist/resources/MCPClientMetadata.js +1 -1
- package/dist/resources/Memory.js +1 -1
- package/dist/resources/MemoryBootstrap.js +1 -1
- package/dist/resources/MemoryCandidate.js +168 -0
- package/dist/resources/MemoryConsolidate.js +1 -1
- package/dist/resources/MemoryDedupStats.js +1 -1
- package/dist/resources/MemoryFeed.js +1 -1
- package/dist/resources/MemoryGrant.js +1 -1
- package/dist/resources/MemoryMaintenance.js +1 -1
- package/dist/resources/MemoryReflect.js +2 -2
- package/dist/resources/MemoryReindex.js +1 -1
- package/dist/resources/MemoryUsage.js +1 -1
- package/dist/resources/OAuth.js +1 -1
- package/dist/resources/OAuthClient.js +1 -1
- package/dist/resources/OrgEvent.js +1 -1
- package/dist/resources/OrgEventCatchup.js +1 -1
- package/dist/resources/OrgEventMaintenance.js +1 -1
- package/dist/resources/PairingToken.js +1 -1
- package/dist/resources/Peer.js +1 -1
- package/dist/resources/Presence.js +3 -3
- package/dist/resources/RecordUsage.js +1 -1
- package/dist/resources/Relationship.js +1 -1
- package/dist/resources/SemanticSearch.js +1 -1
- package/dist/resources/SkillScan.js +1 -1
- package/dist/resources/Soul.js +1 -1
- package/dist/resources/SoulFeed.js +1 -1
- package/dist/resources/WorkspaceLatest.js +1 -1
- package/dist/resources/WorkspaceState.js +1 -1
- package/dist/resources/XAA.js +1 -1
- package/dist/resources/a2a-url.js +1 -1
- package/dist/resources/agent-auth.js +1 -1
- package/dist/resources/agentcard-fields.js +1 -1
- package/dist/resources/auth-middleware.js +1 -1
- package/dist/resources/embeddings-boot.js +4 -4
- package/dist/resources/embeddings-provider.js +5 -5
- package/dist/resources/federation-cleanup.js +5 -5
- package/dist/resources/federation-nonce-store.js +5 -5
- package/dist/resources/health.js +29 -6
- package/dist/resources/instance-identity.js +1 -1
- package/dist/resources/mcp-client-metadata-fields.js +1 -1
- package/dist/resources/mcp-handler.js +1 -1
- package/dist/resources/mcp-oauth.js +2 -2
- package/dist/resources/memory-read-scope.js +1 -1
- package/dist/resources/memory-reflect-lib.js +3 -3
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +1 -1
- package/dist/resources/migrations/embedding-stamp.js +3 -3
- package/dist/resources/migrations/graph-heal.js +2 -2
- package/dist/resources/migrations/ledger.js +1 -1
- package/dist/resources/migrations/registry.js +14 -5
- package/dist/resources/migrations/runner.js +64 -11
- package/dist/resources/migrations/synthetic-test-migration.js +1 -1
- package/dist/resources/migrations/visibility-backfill.js +232 -0
- package/dist/resources/record-types.js +28 -0
- package/dist/resources/semantic-retrieval-core.js +1 -1
- package/dist/resources/usage-recording.js +1 -1
- package/dist/src/rem/scheduler.js +449 -0
- package/docs/deployment.md +1 -0
- package/docs/supply-chain-policy.md +4 -4
- package/docs/upgrade.md +2 -2
- package/package.json +4 -4
- package/schemas/memory.graphql +9 -1
package/dist/cli-shim.cjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The real CLI (dist/cli.js) is an ES module. In ESM, every top-level
|
|
10
10
|
* `import` is HOISTED and the whole module graph is LINKED + EVALUATED before
|
|
11
11
|
* the first statement in the file body runs. Flair's deps (harper-fabric-
|
|
12
|
-
* embeddings requires Node >=22,
|
|
12
|
+
* embeddings requires Node >=22, harper / commander require >=20)
|
|
13
13
|
* fail to load on an older engine — so a Node-version check placed even at the
|
|
14
14
|
* very top of cli.ts never executes: the import graph crashes first. That is
|
|
15
15
|
* exactly the silent-onboarding-failure bug (a Harper dev got zero output and
|
package/dist/cli.js
CHANGED
|
@@ -261,6 +261,40 @@ function readPortFromConfig() {
|
|
|
261
261
|
catch { /* ignore */ }
|
|
262
262
|
return null;
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Read the persisted ops-API bind host from ~/.flair/config.yaml (flair#863).
|
|
266
|
+
* Line-anchored so it can't be satisfied by some other key that merely ends
|
|
267
|
+
* in `opsBind`, and quote-tolerant because a hand-edited config may quote it.
|
|
268
|
+
*
|
|
269
|
+
* `path` is injected the same way launchdPlistPath's `launchAgentsDir` is —
|
|
270
|
+
* so tests exercise the real parse against a temp file instead of the
|
|
271
|
+
* developer's own ~/.flair.
|
|
272
|
+
*/
|
|
273
|
+
function readOpsBindFromConfig(path = configPath()) {
|
|
274
|
+
try {
|
|
275
|
+
if (existsSync(path)) {
|
|
276
|
+
const yaml = readFileSync(path, "utf-8");
|
|
277
|
+
const m = yaml.match(/^\s*opsBind:\s*["']?([^"'\s#]+)["']?/m);
|
|
278
|
+
if (m && m[1])
|
|
279
|
+
return m[1];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch { /* ignore */ }
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
/** Read the persisted ops-API port from ~/.flair/config.yaml (flair#863). */
|
|
286
|
+
function readOpsPortFromConfig(path = configPath()) {
|
|
287
|
+
try {
|
|
288
|
+
if (existsSync(path)) {
|
|
289
|
+
const yaml = readFileSync(path, "utf-8");
|
|
290
|
+
const m = yaml.match(/^\s*opsPort:\s*(\d+)/m);
|
|
291
|
+
if (m)
|
|
292
|
+
return Number(m[1]);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch { /* ignore */ }
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
264
298
|
// Unified port resolution: --port flag > FLAIR_URL env > config file > default
|
|
265
299
|
// Every command that talks to Harper MUST use these helpers.
|
|
266
300
|
function resolveHttpPort(opts) {
|
|
@@ -320,29 +354,60 @@ function resolveOpsPort(opts) {
|
|
|
320
354
|
// Default: httpPort - 1
|
|
321
355
|
return resolveHttpPort(opts) - 1;
|
|
322
356
|
}
|
|
323
|
-
// Ops API bind-host resolution (flair#670): --ops-bind flag >
|
|
324
|
-
// env >
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
// the
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
// Ops API bind-host resolution (flair#670, #863): --ops-bind flag >
|
|
358
|
+
// FLAIR_OPS_BIND env > persisted `opsBind` in ~/.flair/config.yaml > loopback
|
|
359
|
+
// default. This is the escape hatch — deployments that genuinely need remote
|
|
360
|
+
// ops access (multi-host / Fabric) pass an explicit wider address (e.g.
|
|
361
|
+
// `--ops-bind 0.0.0.0`) to opt back in; everything else gets the loopback-only
|
|
362
|
+
// single-host default.
|
|
363
|
+
//
|
|
364
|
+
// The config-file rung (flair#863) is what makes `--ops-bind` durable. Every
|
|
365
|
+
// Harper spawn re-asserts the bind (see opsNetworkPortValue's doc comment for
|
|
366
|
+
// why it MUST), and those spawns happen in `flair start` / `flair restart` /
|
|
367
|
+
// `flair upgrade`, none of which take an `--ops-bind` flag. Without a
|
|
368
|
+
// flair-owned persisted value, a one-off `flair init --ops-bind 0.0.0.0` would
|
|
369
|
+
// be silently reverted to the loopback default by the very next restart — the
|
|
370
|
+
// widening case would be as broken as the narrowing one. Mirrors
|
|
371
|
+
// resolveOpsPort's config rung.
|
|
372
|
+
export function resolveOpsBindHostFrom(flag, envBind, configuredBind) {
|
|
373
|
+
if (flag !== undefined && flag !== null && String(flag).trim() !== "")
|
|
374
|
+
return String(flag).trim();
|
|
337
375
|
if (envBind && envBind.trim() !== "")
|
|
338
376
|
return envBind.trim();
|
|
377
|
+
if (configuredBind && configuredBind.trim() !== "")
|
|
378
|
+
return configuredBind.trim();
|
|
339
379
|
return DEFAULT_OPS_BIND_HOST;
|
|
340
380
|
}
|
|
381
|
+
function resolveOpsBindHost(opts) {
|
|
382
|
+
return resolveOpsBindHostFrom(opts.opsBind, process.env.FLAIR_OPS_BIND, readOpsBindFromConfig());
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The one place that renders Harper's `operationsApi.network.port` value
|
|
386
|
+
* (flair#863). Used by both the HARPER_SET_CONFIG block
|
|
387
|
+
* (buildOperationsApiConfig) and every `OPERATIONSAPI_NETWORK_PORT` env var
|
|
388
|
+
* flair sets, so the two can never disagree about whether the bind host is
|
|
389
|
+
* present.
|
|
390
|
+
*
|
|
391
|
+
* Why every spawn has to carry the host-qualified form, not a bare number:
|
|
392
|
+
* Harper's HARPER_SET_CONFIG handling (harper
|
|
393
|
+
* config/harperConfigEnvVars.ts) records, for each key it force-sets, the
|
|
394
|
+
* value that key had BEFORE the force — `state.originalValues` in
|
|
395
|
+
* `<rootPath>/backup/.harper-config-state.json` — and on the next boot where
|
|
396
|
+
* HARPER_SET_CONFIG is absent, `cleanupRemovedEnvVar` RESTORES those
|
|
397
|
+
* originals into harper-config.yaml. So a bare `OPERATIONSAPI_NETWORK_PORT`
|
|
398
|
+
* anywhere in flair's spawn chain doesn't just lose on that boot: it is
|
|
399
|
+
* latched as the "original" and silently re-widens the bind to all interfaces
|
|
400
|
+
* on the first later boot that omits HARPER_SET_CONFIG. That is exactly what
|
|
401
|
+
* `flair restart` / `flair upgrade` do on the non-launchd path.
|
|
402
|
+
*/
|
|
403
|
+
export function opsNetworkPortValue(opsBindHost, opsPort) {
|
|
404
|
+
return `${opsBindHost}:${opsPort}`;
|
|
405
|
+
}
|
|
341
406
|
/**
|
|
342
407
|
* Build the `operationsApi` block for Harper's HARPER_SET_CONFIG (flair#670).
|
|
343
408
|
*
|
|
344
409
|
* `network.port` uses Harper's "host:port" string form — Harper's server
|
|
345
|
-
* bootstrap (
|
|
410
|
+
* bootstrap (harper dist/server/threads/threadServer.js,
|
|
346
411
|
* listenOnPorts/listenOnPortsBun) splits a config port value on its last
|
|
347
412
|
* `:` into an explicit bind host + port when present, and falls back to
|
|
348
413
|
* binding all interfaces (0.0.0.0 / ::) when given a bare number. A colon-free
|
|
@@ -350,15 +415,48 @@ function resolveOpsBindHost(opts) {
|
|
|
350
415
|
* it with a host is the only config-level way to narrow the bind.
|
|
351
416
|
*
|
|
352
417
|
* `domainSocket` lives under `network` per Harper's own config schema
|
|
353
|
-
* (
|
|
418
|
+
* (harper/config-root.schema.json → properties.operationsApi
|
|
354
419
|
* .properties.network.properties.domainSocket, and
|
|
355
420
|
* dist/validation/configValidator.js's `operationsApi.network.domainSocket`
|
|
356
421
|
* Joi path) — nested here, not as a sibling of `network`.
|
|
357
422
|
*/
|
|
358
423
|
export function buildOperationsApiConfig(opsPort, opsSocket, opsBindHost) {
|
|
359
424
|
return {
|
|
360
|
-
network: { port:
|
|
425
|
+
network: { port: opsNetworkPortValue(opsBindHost, opsPort), cors: true, domainSocket: opsSocket },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Build the flair-owned environment overrides for a DIRECT (non-launchd)
|
|
430
|
+
* Harper spawn (flair#863) — shared by `flair start`'s fallback path and
|
|
431
|
+
* startFlairProcess() (which backs `flair restart` and `flair upgrade`).
|
|
432
|
+
* Callers spread this over `process.env`.
|
|
433
|
+
*
|
|
434
|
+
* These two sites used to build the env inline and had drifted: `start` set a
|
|
435
|
+
* host-qualified OPERATIONSAPI_NETWORK_PORT, `startFlairProcess` set none at
|
|
436
|
+
* all. Neither path sets HARPER_SET_CONFIG, and Harper restores the
|
|
437
|
+
* pre-SET_CONFIG original for every key SET_CONFIG had forced whenever that
|
|
438
|
+
* variable is absent — so the site that omitted the var silently re-widened
|
|
439
|
+
* the ops API to all interfaces on every restart/upgrade, and persisted it.
|
|
440
|
+
* One builder means the next spawn site cannot reintroduce that gap.
|
|
441
|
+
*
|
|
442
|
+
* Deliberately omits HDB_ADMIN_PASSWORD when no password is in hand: an empty
|
|
443
|
+
* string would strip Harper's auth on an existing install.
|
|
444
|
+
*/
|
|
445
|
+
export function buildDirectSpawnEnv(opts) {
|
|
446
|
+
const env = {
|
|
447
|
+
ROOTPATH: opts.dataDir,
|
|
448
|
+
// The embedding backend self-registers in-process at boot
|
|
449
|
+
// (resources/embeddings-boot.ts); this only tells it where the model lives.
|
|
450
|
+
FLAIR_MODELS_DIR: opts.modelsDir,
|
|
451
|
+
DEFAULTS_MODE: "dev",
|
|
452
|
+
HDB_ADMIN_USERNAME: opts.adminUser,
|
|
453
|
+
HTTP_PORT: String(opts.httpPort),
|
|
454
|
+
OPERATIONSAPI_NETWORK_PORT: opsNetworkPortValue(opts.opsBindHost, opts.opsPort),
|
|
455
|
+
LOCAL_STUDIO: "false",
|
|
361
456
|
};
|
|
457
|
+
if (opts.adminPass)
|
|
458
|
+
env.HDB_ADMIN_PASSWORD = opts.adminPass;
|
|
459
|
+
return env;
|
|
362
460
|
}
|
|
363
461
|
/**
|
|
364
462
|
* Decide whether a persisted `operationsApi.network.port` value (read back
|
|
@@ -693,10 +791,26 @@ function resolveOpsUrlFromTarget(targetUrl) {
|
|
|
693
791
|
}
|
|
694
792
|
return url.toString().replace(/\/$/, "");
|
|
695
793
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
794
|
+
/**
|
|
795
|
+
* Persist the instance coordinates other commands need to find and re-assert
|
|
796
|
+
* this install (flair#863).
|
|
797
|
+
*
|
|
798
|
+
* This rewrites the file wholesale, so `opsPort`/`opsBind` default to whatever
|
|
799
|
+
* is already persisted rather than being dropped — otherwise an unrelated
|
|
800
|
+
* caller (e.g. `flair doctor --fix` correcting a drifted HTTP port) would
|
|
801
|
+
* silently erase the operator's `--ops-bind` choice and the next restart would
|
|
802
|
+
* revert the bind.
|
|
803
|
+
*/
|
|
804
|
+
function writeConfig(port, opsPort, opsBind, path = configPath()) {
|
|
805
|
+
const resolvedOpsPort = opsPort ?? readOpsPortFromConfig(path);
|
|
806
|
+
const resolvedOpsBind = opsBind ?? readOpsBindFromConfig(path);
|
|
807
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
808
|
+
let body = `# Flair configuration\nport: ${port}\n`;
|
|
809
|
+
if (resolvedOpsPort !== null && resolvedOpsPort !== undefined)
|
|
810
|
+
body += `opsPort: ${resolvedOpsPort}\n`;
|
|
811
|
+
if (resolvedOpsBind)
|
|
812
|
+
body += `opsBind: ${resolvedOpsBind}\n`;
|
|
813
|
+
writeFileSync(path, body);
|
|
700
814
|
}
|
|
701
815
|
function privKeyPath(agentId, keysDir) {
|
|
702
816
|
return join(keysDir, `${agentId}.key`);
|
|
@@ -709,11 +823,25 @@ function flairPackageDir() {
|
|
|
709
823
|
return join(import.meta.dirname ?? __dirname, "..");
|
|
710
824
|
}
|
|
711
825
|
function harperBin() {
|
|
712
|
-
// Resolve relative to this file's location (dist/cli.js → ../node_modules/...)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
826
|
+
// Resolve relative to this file's location (dist/cli.js → ../node_modules/...).
|
|
827
|
+
//
|
|
828
|
+
// flair#870: flair depends on the BARE `harper` package name. The scoped
|
|
829
|
+
// `@harperfast/harper` segments are kept as a fallback so an in-place upgrade
|
|
830
|
+
// over a pre-#870 install (whose node_modules still holds the scoped copy,
|
|
831
|
+
// and whose Harper is the one currently serving the data dir) keeps booting
|
|
832
|
+
// until the next clean install. Bare name is tried first so a tree that has
|
|
833
|
+
// BOTH resolves to the one flair actually declares.
|
|
834
|
+
const roots = [
|
|
835
|
+
join(import.meta.dirname ?? __dirname, ".."),
|
|
836
|
+
process.cwd(),
|
|
716
837
|
];
|
|
838
|
+
const pkgSegments = [["harper"], ["@harperfast", "harper"]];
|
|
839
|
+
const candidates = [];
|
|
840
|
+
for (const root of roots) {
|
|
841
|
+
for (const segs of pkgSegments) {
|
|
842
|
+
candidates.push(join(root, "node_modules", ...segs, "dist", "bin", "harper.js"));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
717
845
|
for (const c of candidates)
|
|
718
846
|
if (existsSync(c))
|
|
719
847
|
return c;
|
|
@@ -2302,8 +2430,13 @@ program
|
|
|
2302
2430
|
catch { /* not running */ }
|
|
2303
2431
|
if (!alreadyRunning) {
|
|
2304
2432
|
const bin = harperBin();
|
|
2305
|
-
if (!bin)
|
|
2306
|
-
throw new Error("
|
|
2433
|
+
if (!bin) {
|
|
2434
|
+
throw new Error("Harper CLI not found: no dist/bin/harper.js under node_modules/harper " +
|
|
2435
|
+
"(or the legacy node_modules/@harperfast/harper) next to this flair " +
|
|
2436
|
+
`install or in ${process.cwd()}.\n` +
|
|
2437
|
+
"Flair ships Harper as a dependency, so this normally means a partial " +
|
|
2438
|
+
"or interrupted install.\nFix: reinstall flair — npm install -g @tpsdev-ai/flair");
|
|
2439
|
+
}
|
|
2307
2440
|
mkdirSync(dataDir, { recursive: true });
|
|
2308
2441
|
// Detect whether Harper has already been installed in this data dir.
|
|
2309
2442
|
// harper-config.yaml is created during install — its presence means
|
|
@@ -2338,7 +2471,12 @@ program
|
|
|
2338
2471
|
THREADS_COUNT: "1",
|
|
2339
2472
|
NODE_HOSTNAME: "localhost",
|
|
2340
2473
|
HTTP_PORT: String(httpPort),
|
|
2341
|
-
|
|
2474
|
+
// flair#863: host-qualified, NOT a bare port. A bare value here is
|
|
2475
|
+
// what Harper latches as `originalValues["operationsApi.network.port"]`
|
|
2476
|
+
// when HARPER_SET_CONFIG force-sets the same key — and restores on the
|
|
2477
|
+
// first later boot without HARPER_SET_CONFIG, re-widening the bind to
|
|
2478
|
+
// all interfaces. See opsNetworkPortValue's doc comment.
|
|
2479
|
+
OPERATIONSAPI_NETWORK_PORT: opsNetworkPortValue(opsBindHost, opsPort),
|
|
2342
2480
|
LOCAL_STUDIO: "false",
|
|
2343
2481
|
};
|
|
2344
2482
|
// models (flair#504 Phase 1): the embedding backend registers itself
|
|
@@ -2478,7 +2616,7 @@ program
|
|
|
2478
2616
|
<key>THREADS_COUNT</key><string>1</string>
|
|
2479
2617
|
<key>NODE_HOSTNAME</key><string>localhost</string>
|
|
2480
2618
|
<key>HTTP_PORT</key><string>${httpPort}</string>
|
|
2481
|
-
<key>OPERATIONSAPI_NETWORK_PORT</key><string>${opsPort}</string>
|
|
2619
|
+
<key>OPERATIONSAPI_NETWORK_PORT</key><string>${escapeXml(opsNetworkPortValue(opsBindHost, opsPort))}</string>
|
|
2482
2620
|
<key>LOCAL_STUDIO</key><string>false</string>
|
|
2483
2621
|
</dict>
|
|
2484
2622
|
<key>RunAtLoad</key><true/>
|
|
@@ -2492,8 +2630,15 @@ program
|
|
|
2492
2630
|
}
|
|
2493
2631
|
}
|
|
2494
2632
|
}
|
|
2495
|
-
// Persist
|
|
2496
|
-
|
|
2633
|
+
// Persist the instance coordinates so other commands can find AND
|
|
2634
|
+
// re-assert this instance. flair#863: `opsBind` in particular has to be
|
|
2635
|
+
// persisted here, not just handed to this run's spawn — `flair start` /
|
|
2636
|
+
// `flair restart` / `flair upgrade` re-assert the bind on every spawn and
|
|
2637
|
+
// have no `--ops-bind` flag of their own, so this is the only thing that
|
|
2638
|
+
// survives an `--ops-bind` choice past the next restart. It also runs on
|
|
2639
|
+
// the already-running path (where init skips the spawn entirely), which is
|
|
2640
|
+
// what makes doctor's `flair init && flair restart` remedy actually apply.
|
|
2641
|
+
writeConfig(httpPort, opsPort, opsBindHost);
|
|
2497
2642
|
if (agentId) {
|
|
2498
2643
|
// Generate or reuse keypair
|
|
2499
2644
|
mkdirSync(keysDir, { recursive: true });
|
|
@@ -6423,27 +6568,17 @@ remNightly
|
|
|
6423
6568
|
const minute = parseInt(match[2], 10);
|
|
6424
6569
|
const port = readPortFromConfig() ?? DEFAULT_PORT;
|
|
6425
6570
|
const flairUrl = opts.flairUrl || process.env.FLAIR_URL || `http://127.0.0.1:${port}`;
|
|
6426
|
-
const { enableScheduler } = await import("./rem/scheduler.js");
|
|
6571
|
+
const { enableScheduler, formatEnableReport } = await import("./rem/scheduler.js");
|
|
6427
6572
|
try {
|
|
6428
6573
|
const r = enableScheduler({ agentId, flairUrl, hour, minute });
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
if (
|
|
6436
|
-
|
|
6437
|
-
console.log(` Load: ${r.loadCommand.join(" ")} → ok`);
|
|
6438
|
-
}
|
|
6439
|
-
else {
|
|
6440
|
-
console.log(` Load: ${r.loadCommand.join(" ")} → code ${r.loadResult.code}`);
|
|
6441
|
-
if (r.loadResult.stderr)
|
|
6442
|
-
console.log(` stderr: ${r.loadResult.stderr.trim()}`);
|
|
6443
|
-
}
|
|
6444
|
-
}
|
|
6445
|
-
console.log(`\nTip: run \`flair rem nightly run-once --dry-run\` to verify the cycle works`);
|
|
6446
|
-
console.log(` before the first scheduled fire. Disable with \`flair rem nightly disable\`.`);
|
|
6574
|
+
// formatEnableReport() owns the success-vs-failure decision (flair#850:
|
|
6575
|
+
// do not print a success headline before activation is known to have
|
|
6576
|
+
// succeeded) — see src/rem/scheduler.ts for the unit-tested logic.
|
|
6577
|
+
const { lines, ok } = formatEnableReport(r, { hour, minute, agentId, flairUrl });
|
|
6578
|
+
for (const line of lines)
|
|
6579
|
+
console.log(line);
|
|
6580
|
+
if (!ok)
|
|
6581
|
+
process.exit(1);
|
|
6447
6582
|
}
|
|
6448
6583
|
catch (err) {
|
|
6449
6584
|
console.error(`Error: ${err.message}`);
|
|
@@ -6481,18 +6616,14 @@ remNightly
|
|
|
6481
6616
|
});
|
|
6482
6617
|
remNightly
|
|
6483
6618
|
.command("status")
|
|
6484
|
-
.description("Show whether the nightly scheduler is installed")
|
|
6619
|
+
.description("Show whether the nightly scheduler is installed and genuinely active")
|
|
6485
6620
|
.action(async () => {
|
|
6486
|
-
const { schedulerStatus } = await import("./rem/scheduler.js");
|
|
6621
|
+
const { schedulerStatus, formatStatusReport } = await import("./rem/scheduler.js");
|
|
6487
6622
|
try {
|
|
6488
6623
|
const s = schedulerStatus();
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
console.log(` Shim: ${s.shimPath}${s.shimExists ? "" : " (missing)"}`);
|
|
6493
|
-
if (!s.installed) {
|
|
6494
|
-
console.log(`\nEnable with: flair rem nightly enable --agent <id> [--at HH:MM]`);
|
|
6495
|
-
}
|
|
6624
|
+
const { lines } = formatStatusReport(s);
|
|
6625
|
+
for (const line of lines)
|
|
6626
|
+
console.log(line);
|
|
6496
6627
|
}
|
|
6497
6628
|
catch (err) {
|
|
6498
6629
|
console.error(`Error: ${err.message}`);
|
|
@@ -7650,7 +7781,7 @@ export function resolveFabricCredentials(opts) {
|
|
|
7650
7781
|
// Mirrors `flair deploy`'s credential handling (FABRIC_USER/FABRIC_PASSWORD env
|
|
7651
7782
|
// fallbacks, password-via-flag warning, --fabric-password-file — see
|
|
7652
7783
|
// resolveFabricCredentials above) and NEVER prints credentials. The
|
|
7653
|
-
// version-resolution +
|
|
7784
|
+
// version-resolution + harper pin + reuse of deploy() lives in
|
|
7654
7785
|
// src/fabric-upgrade.ts; this wrapper only does CLI plumbing + the confirm.
|
|
7655
7786
|
async function runFabricUpgrade(opts) {
|
|
7656
7787
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
@@ -7758,7 +7889,7 @@ async function runFabricUpgrade(opts) {
|
|
|
7758
7889
|
// before the package swap, with a keep-last-3 retention policy.
|
|
7759
7890
|
//
|
|
7760
7891
|
// Native-backup alternative considered and rejected: Harper ships a
|
|
7761
|
-
// `get_backup` operation (
|
|
7892
|
+
// `get_backup` operation (harper's dataLayer/getBackup.ts,
|
|
7762
7893
|
// wired in server/serverHelpers/serverUtilities.ts, documented in
|
|
7763
7894
|
// components/mcp/tools/schemas/operationDescriptions.ts) that streams a
|
|
7764
7895
|
// live backup over the running HTTP operations API. It's available in this
|
|
@@ -8104,13 +8235,13 @@ program
|
|
|
8104
8235
|
// ── Fabric upgrade (--target) ────────────────────────────────────────────
|
|
8105
8236
|
// When --target is passed, upgrade the Flair component DEPLOYED to that
|
|
8106
8237
|
// Harper Fabric URL instead of the local npm install. Reuses `flair deploy`
|
|
8107
|
-
// under the hood with the
|
|
8238
|
+
// under the hood with the harper pin baked in (flair#513).
|
|
8108
8239
|
.option("--target <url>", "Upgrade the Flair deployed to this Fabric URL (not the local install)")
|
|
8109
8240
|
.option("--fabric-user <user>", "Fabric admin username — for --target (env: FABRIC_USER preferred; inline leaks to shell history)")
|
|
8110
8241
|
.option("--fabric-password <pass>", "Fabric admin password — for --target (prefer FABRIC_PASSWORD env or --fabric-password-file; inline leaks to shell history)")
|
|
8111
8242
|
.option("--fabric-password-file <path>", "Read the Fabric admin password from a file (chmod 600) — for --target")
|
|
8112
8243
|
.option("--version <semver>", "Flair version to deploy with --target (default: latest published @tpsdev-ai/flair)")
|
|
8113
|
-
.option("--harper-version <semver>", "Pin
|
|
8244
|
+
.option("--harper-version <semver>", "Pin harper to this version for --target (default: registry latest, floored at the flair#513 fix)")
|
|
8114
8245
|
.option("--project <name>", "Fabric component name for --target", "flair")
|
|
8115
8246
|
.option("--no-replicated", "Disable cluster-wide replication for --target (default: replicated=true)")
|
|
8116
8247
|
.option("--yes", "Skip the confirmation prompt for --target")
|
|
@@ -8668,34 +8799,23 @@ program
|
|
|
8668
8799
|
process.exit(1);
|
|
8669
8800
|
}
|
|
8670
8801
|
const adminPass = process.env.HDB_ADMIN_PASSWORD || process.env.FLAIR_ADMIN_PASS || "";
|
|
8671
|
-
|
|
8672
|
-
//
|
|
8673
|
-
//
|
|
8674
|
-
//
|
|
8675
|
-
//
|
|
8676
|
-
// every plain `flair start`. Re-resolving the same host:port form init uses
|
|
8677
|
-
// keeps this re-assertion consistent instead of regressing it — the
|
|
8678
|
-
// escape hatch on this path is FLAIR_OPS_BIND (no --ops-bind flag on `start`).
|
|
8679
|
-
const opsBindHost = resolveOpsBindHost({});
|
|
8680
|
-
const modelsDir = process.env.FLAIR_MODELS_DIR ?? join(dataDir, "models");
|
|
8802
|
+
// flair#670/#863: this fallback path (no launchd plist) sets no
|
|
8803
|
+
// HARPER_SET_CONFIG, so the ops bind has to be re-asserted explicitly on
|
|
8804
|
+
// every spawn — see buildDirectSpawnEnv. The escape hatch on this path is
|
|
8805
|
+
// FLAIR_OPS_BIND or the `opsBind` that `flair init --ops-bind` persisted to
|
|
8806
|
+
// ~/.flair/config.yaml; there is no --ops-bind flag on `start`.
|
|
8681
8807
|
const env = {
|
|
8682
8808
|
...process.env,
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8809
|
+
...buildDirectSpawnEnv({
|
|
8810
|
+
dataDir,
|
|
8811
|
+
modelsDir: process.env.FLAIR_MODELS_DIR ?? join(dataDir, "models"),
|
|
8812
|
+
httpPort: port,
|
|
8813
|
+
opsPort: resolveOpsPort(opts),
|
|
8814
|
+
opsBindHost: resolveOpsBindHost({}),
|
|
8815
|
+
adminUser: DEFAULT_ADMIN_USER,
|
|
8816
|
+
adminPass,
|
|
8817
|
+
}),
|
|
8691
8818
|
};
|
|
8692
|
-
// Only set HDB_ADMIN_PASSWORD if we have a real value — empty string
|
|
8693
|
-
// would strip Harper's auth on an existing install
|
|
8694
|
-
if (adminPass) {
|
|
8695
|
-
env.HDB_ADMIN_PASSWORD = adminPass;
|
|
8696
|
-
}
|
|
8697
|
-
// models (flair#504 Phase 1): no env var needed — resources/embeddings-boot.ts
|
|
8698
|
-
// self-registers the backend in-process on every boot (flair#694).
|
|
8699
8819
|
const proc = spawn(process.execPath, [bin, "run", "."], {
|
|
8700
8820
|
cwd: flairPackageDir(), env, detached: true, stdio: "ignore",
|
|
8701
8821
|
});
|
|
@@ -8820,22 +8940,29 @@ async function startFlairProcess(port) {
|
|
|
8820
8940
|
// to the initial Harper spawn) followed by `flair restart` would silently
|
|
8821
8941
|
// drop admin credentials — any subsequent auth'd call returns 401.
|
|
8822
8942
|
const adminPass = process.env.HDB_ADMIN_PASSWORD || process.env.FLAIR_ADMIN_PASS || "";
|
|
8823
|
-
|
|
8943
|
+
// flair#863: the same env `flair start` builds, from the same builder — this
|
|
8944
|
+
// path sets no HARPER_SET_CONFIG, and Harper RESTORES the pre-SET_CONFIG
|
|
8945
|
+
// original for every key SET_CONFIG had forced whenever that variable is
|
|
8946
|
+
// absent (cleanupRemovedEnvVar; see opsNetworkPortValue). This site used to
|
|
8947
|
+
// set no OPERATIONSAPI_NETWORK_PORT at all, so every `flair restart` /
|
|
8948
|
+
// `flair upgrade` on the non-launchd path silently reverted
|
|
8949
|
+
// `operationsApi.network.port` to a bare number in harper-config.yaml and
|
|
8950
|
+
// re-bound the ops API to all interfaces — permanently, since the reverted
|
|
8951
|
+
// value is what the next boot reads. It also made doctor's
|
|
8952
|
+
// `flair init && flair restart` remedy a no-op: the restart undid whatever
|
|
8953
|
+
// init had just written.
|
|
8824
8954
|
const env = {
|
|
8825
8955
|
...process.env,
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8956
|
+
...buildDirectSpawnEnv({
|
|
8957
|
+
dataDir,
|
|
8958
|
+
modelsDir: process.env.FLAIR_MODELS_DIR ?? join(dataDir, "models"),
|
|
8959
|
+
httpPort: port,
|
|
8960
|
+
opsPort: resolveOpsPort({ port }),
|
|
8961
|
+
opsBindHost: resolveOpsBindHost({}),
|
|
8962
|
+
adminUser: DEFAULT_ADMIN_USER,
|
|
8963
|
+
adminPass,
|
|
8964
|
+
}),
|
|
8833
8965
|
};
|
|
8834
|
-
if (adminPass) {
|
|
8835
|
-
env.HDB_ADMIN_PASSWORD = adminPass;
|
|
8836
|
-
}
|
|
8837
|
-
// models (flair#504 Phase 1): no env var needed — resources/embeddings-boot.ts
|
|
8838
|
-
// self-registers the backend in-process on every boot (flair#694).
|
|
8839
8966
|
const proc = spawn(process.execPath, [bin, "run", "."], {
|
|
8840
8967
|
cwd: flairPackageDir(), env, detached: true, stdio: "ignore",
|
|
8841
8968
|
});
|
|
@@ -9052,7 +9179,7 @@ program
|
|
|
9052
9179
|
// through the HNSW cosine index, which throws "Cosine distance comparison
|
|
9053
9180
|
// requires an array" against rows whose stored embedding shape is
|
|
9054
9181
|
// incompatible with the running Harper version (e.g. data written under
|
|
9055
|
-
//
|
|
9182
|
+
// harper@5.0.1 read under 5.0.9). The ops API bypasses the
|
|
9056
9183
|
// vector index — exactly what we need when the goal is to replace every
|
|
9057
9184
|
// embedding with a freshly-computed one. Without this path, `flair
|
|
9058
9185
|
// reembed` could not recover from the very condition it exists to fix.
|
|
@@ -9487,7 +9614,7 @@ Exit codes:
|
|
|
9487
9614
|
|
|
9488
9615
|
"peer" here means a Flair federation peer (GET /FederationPeers on the
|
|
9489
9616
|
origin) — NOT Harper's own cluster-replication nodes, which the OSS
|
|
9490
|
-
|
|
9617
|
+
harper build this CLI ships does not expose (cluster_status is
|
|
9491
9618
|
a harper-pro-only operation). A Fabric replica that was never
|
|
9492
9619
|
federation-paired (\`flair federation pair\`) is invisible to this sweep —
|
|
9493
9620
|
see src/fleet-verify.ts's file header for the full caveat.`)
|
|
@@ -13862,6 +13989,6 @@ if (import.meta.main) {
|
|
|
13862
13989
|
await runCli();
|
|
13863
13990
|
}
|
|
13864
13991
|
// ─── Exported for testing ─────────────────────────────────────────────────────
|
|
13865
|
-
export { runCli, resolveKeyPath, buildEd25519Auth, readPortFromConfig, resolveHttpPort, resolveOpsPort, resolveOpsBindHost, resolveTarget, resolveOpsTarget, resolveEffectiveOpsUrl, resolveOpsUrlFromTarget, signRequestBody, b64, b64url, program, api, VALID_PRESENCE_ACTIVITIES, MAX_TASK_LENGTH, MAX_WORKSPACE_FIELD_LENGTH, MAX_ORGEVENT_SUMMARY_LENGTH, MAX_ORGEVENT_DETAIL_LENGTH, isLocalBase, isLikelyRealSecret, shouldShowInlineSecretWarning, parseTokenFromFile, resolveLocalAdminPass, readAdminPassFileSecure,
|
|
13992
|
+
export { runCli, resolveKeyPath, buildEd25519Auth, readPortFromConfig, readOpsBindFromConfig, readOpsPortFromConfig, writeConfig, resolveHttpPort, resolveOpsPort, resolveOpsBindHost, resolveTarget, resolveOpsTarget, resolveEffectiveOpsUrl, resolveOpsUrlFromTarget, signRequestBody, b64, b64url, program, api, VALID_PRESENCE_ACTIVITIES, MAX_TASK_LENGTH, MAX_WORKSPACE_FIELD_LENGTH, MAX_ORGEVENT_SUMMARY_LENGTH, MAX_ORGEVENT_DETAIL_LENGTH, isLocalBase, isLikelyRealSecret, shouldShowInlineSecretWarning, parseTokenFromFile, resolveLocalAdminPass, readAdminPassFileSecure,
|
|
13866
13993
|
// launchd label (flair#693)
|
|
13867
13994
|
LEGACY_LAUNCHD_LABEL, launchdLabel, launchdPlistPath, resolveLaunchdLabel, migrateLegacyLaunchdLabel, ensureLaunchdServiceLoaded, };
|
package/dist/deploy.js
CHANGED
|
@@ -156,28 +156,38 @@ export function deriveVerifyResources(packageRoot) {
|
|
|
156
156
|
names.sort();
|
|
157
157
|
return names.length ? names : [FALLBACK_VERIFY_RESOURCE];
|
|
158
158
|
}
|
|
159
|
+
// flair#870: flair declares the BARE `harper` package name, so that is probed
|
|
160
|
+
// first. `@harperfast/harper` is a permanent lockstep publish of the same
|
|
161
|
+
// source under the legacy scoped name; it stays in the probe list because
|
|
162
|
+
// `packageRoot` can be a staged install of an OLDER published flair (see
|
|
163
|
+
// src/fabric-upgrade.ts) that declared the scoped name.
|
|
164
|
+
const HARPER_PKG_NAMES = ["harper", "@harperfast/harper"];
|
|
159
165
|
function resolveHarperBin(packageRoot) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const req = createRequire(join(packageRoot, "package.json"));
|
|
165
|
-
const mainPath = req.resolve("@harperfast/harper");
|
|
166
|
-
let dir = dirname(mainPath);
|
|
167
|
-
for (let i = 0; i < 6; i++) {
|
|
168
|
-
const candidate = join(dir, "dist/bin/harper.js");
|
|
169
|
-
if (existsSync(candidate))
|
|
170
|
-
return candidate;
|
|
171
|
-
const parent = dirname(dir);
|
|
172
|
-
if (parent === dir)
|
|
173
|
-
break;
|
|
174
|
-
dir = parent;
|
|
175
|
-
}
|
|
166
|
+
for (const pkg of HARPER_PKG_NAMES) {
|
|
167
|
+
const local = join(packageRoot, "node_modules", pkg, "dist/bin/harper.js");
|
|
168
|
+
if (existsSync(local))
|
|
169
|
+
return local;
|
|
176
170
|
}
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
for (const pkg of HARPER_PKG_NAMES) {
|
|
172
|
+
try {
|
|
173
|
+
const req = createRequire(join(packageRoot, "package.json"));
|
|
174
|
+
const mainPath = req.resolve(pkg);
|
|
175
|
+
let dir = dirname(mainPath);
|
|
176
|
+
for (let i = 0; i < 6; i++) {
|
|
177
|
+
const candidate = join(dir, "dist/bin/harper.js");
|
|
178
|
+
if (existsSync(candidate))
|
|
179
|
+
return candidate;
|
|
180
|
+
const parent = dirname(dir);
|
|
181
|
+
if (parent === dir)
|
|
182
|
+
break;
|
|
183
|
+
dir = parent;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
/* try the next package name */
|
|
188
|
+
}
|
|
179
189
|
}
|
|
180
|
-
throw new Error(
|
|
190
|
+
throw new Error(`Could not locate Harper CLI binary (tried ${HARPER_PKG_NAMES.join(", ")}). ` +
|
|
181
191
|
"Flair deploy requires Harper to be installed alongside Flair.");
|
|
182
192
|
}
|
|
183
193
|
// Pure arg-array builder — separated from spawnHarper so the timeout
|