@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/fabric-upgrade.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* Upgrading a deployed Fabric Flair used to require, by hand:
|
|
10
10
|
* 1. mkdir a fresh temp dir
|
|
11
11
|
* 2. write a package.json that depends on @tpsdev-ai/flair@<version> AND
|
|
12
|
-
* carries an `overrides` block pinning
|
|
12
|
+
* carries an `overrides` block pinning harper to a fixed
|
|
13
13
|
* version — because the PUBLISHED flair declares an OLD Harper
|
|
14
|
-
* (
|
|
14
|
+
* (harper@5.0.21 as of flair@0.14.0) whose component
|
|
15
15
|
* packager (`packageComponent`) emits an EMPTY tarball when the package
|
|
16
16
|
* root lives under node_modules (the real npm-install scenario).
|
|
17
17
|
* See flair#513 — fixed in Harper >= 5.1.13.
|
|
@@ -31,7 +31,7 @@ import { join } from "node:path";
|
|
|
31
31
|
import { tmpdir } from "node:os";
|
|
32
32
|
import { createRequire } from "node:module";
|
|
33
33
|
/**
|
|
34
|
-
* Minimum
|
|
34
|
+
* Minimum harper version whose component packager works when the
|
|
35
35
|
* package root is under node_modules (flair#513 — the empty-tarball fix landed
|
|
36
36
|
* in 5.1.13). Anything below this MUST be overridden before a Fabric deploy.
|
|
37
37
|
*/
|
|
@@ -43,7 +43,18 @@ export const MIN_HARPER_VERSION = "5.1.13";
|
|
|
43
43
|
*/
|
|
44
44
|
export const DEFAULT_HARPER_PIN = "5.1.14";
|
|
45
45
|
const FLAIR_PKG = "@tpsdev-ai/flair";
|
|
46
|
-
const HARPER_PKG = "
|
|
46
|
+
const HARPER_PKG = "harper";
|
|
47
|
+
/**
|
|
48
|
+
* flair#870 renamed flair's Harper dependency from the scoped
|
|
49
|
+
* `@harperfast/harper` to the BARE `harper` name (permanent lockstep publishes
|
|
50
|
+
* of the same source; bare is upstream's primary public name).
|
|
51
|
+
*
|
|
52
|
+
* This module reads and rewrites the dependency graph of a PUBLISHED flair
|
|
53
|
+
* version, which may predate that rename — so every lookup here has to accept
|
|
54
|
+
* either name. The bare name is always preferred when both are present.
|
|
55
|
+
*/
|
|
56
|
+
const LEGACY_HARPER_PKG = "@harperfast/harper";
|
|
57
|
+
const HARPER_PKG_NAMES = [HARPER_PKG, LEGACY_HARPER_PKG];
|
|
47
58
|
/**
|
|
48
59
|
* Parse "5.1.14" / "5.1.14-rc.1" / "v0.14.0" into [major, minor, patch],
|
|
49
60
|
* ignoring any pre-release / build suffix for comparison. Returns null when the
|
|
@@ -76,7 +87,7 @@ export function semverGte(a, b) {
|
|
|
76
87
|
return true; // equal
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
|
-
* Decide what (if any)
|
|
90
|
+
* Decide what (if any) harper override to bake into the temp
|
|
80
91
|
* deployable's package.json.
|
|
81
92
|
*
|
|
82
93
|
* - If flair already declares Harper >= MIN_HARPER_VERSION → no override
|
|
@@ -88,7 +99,7 @@ export function semverGte(a, b) {
|
|
|
88
99
|
* passed something too old.
|
|
89
100
|
*
|
|
90
101
|
* Pure — no I/O. The LOAD-BEARING bit is `overridden`: when true, the temp
|
|
91
|
-
* package.json gets `overrides: { "
|
|
102
|
+
* package.json gets `overrides: { "harper": pin }`.
|
|
92
103
|
*/
|
|
93
104
|
export function resolveHarperPin(declared, preferredPin) {
|
|
94
105
|
if (declared && semverGte(declared, MIN_HARPER_VERSION)) {
|
|
@@ -120,6 +131,13 @@ export function resolveHarperPin(declared, preferredPin) {
|
|
|
120
131
|
* @tpsdev-ai/flair@<version> and, when the Harper pin is an override, carries
|
|
121
132
|
* the `overrides` block that forces npm to install the fix Harper under the
|
|
122
133
|
* flair package's node_modules.
|
|
134
|
+
*
|
|
135
|
+
* The override is written under BOTH Harper package names (flair#870). Only
|
|
136
|
+
* flair versions OLDER than the rename ever need an override — MIN_HARPER_VERSION
|
|
137
|
+
* is 5.1.13 and every post-rename flair declares ≥ 5.1.22 — and those declare
|
|
138
|
+
* the scoped name, so dropping the legacy key would make the override a silent
|
|
139
|
+
* no-op exactly when it matters. Both are plain VERSION overrides, not `npm:`
|
|
140
|
+
* aliases; an override key that matches nothing in the tree is inert.
|
|
123
141
|
*/
|
|
124
142
|
export function buildDeployablePackageJson(flairVersion, pin) {
|
|
125
143
|
const pkg = {
|
|
@@ -131,7 +149,7 @@ export function buildDeployablePackageJson(flairVersion, pin) {
|
|
|
131
149
|
},
|
|
132
150
|
};
|
|
133
151
|
if (pin.overridden) {
|
|
134
|
-
pkg.overrides =
|
|
152
|
+
pkg.overrides = Object.fromEntries(HARPER_PKG_NAMES.map((n) => [n, pin.pin]));
|
|
135
153
|
}
|
|
136
154
|
return pkg;
|
|
137
155
|
}
|
|
@@ -156,7 +174,14 @@ async function defaultFetchDeclaredHarperVersion(flairVersion) {
|
|
|
156
174
|
if (!res.ok)
|
|
157
175
|
return null;
|
|
158
176
|
const data = (await res.json());
|
|
159
|
-
|
|
177
|
+
// Either package name (flair#870) — pre-rename flair versions declare the
|
|
178
|
+
// scoped one, and those are precisely the versions that may need an override.
|
|
179
|
+
for (const name of HARPER_PKG_NAMES) {
|
|
180
|
+
const declared = data.dependencies?.[name];
|
|
181
|
+
if (declared)
|
|
182
|
+
return declared;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
160
185
|
}
|
|
161
186
|
function defaultNpmInstall(dir) {
|
|
162
187
|
// No package spec on argv — npm reads dependencies + overrides from the
|
|
@@ -205,13 +230,22 @@ async function defaultFetchDeployedVersion(opts) {
|
|
|
205
230
|
return null;
|
|
206
231
|
}
|
|
207
232
|
}
|
|
208
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* Resolve the harper bin that npm installed under the staged flair.
|
|
235
|
+
*
|
|
236
|
+
* Probes both Harper package names (flair#870): the staged flair may be a
|
|
237
|
+
* pre-rename published version whose Harper landed under `@harperfast/harper`.
|
|
238
|
+
*/
|
|
209
239
|
export function resolveStagedHarperVersion(stagingDir) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
240
|
+
const candidates = [];
|
|
241
|
+
for (const name of HARPER_PKG_NAMES) {
|
|
242
|
+
// Nested under the staged flair…
|
|
243
|
+
candidates.push(join(stagingDir, "node_modules", FLAIR_PKG, "node_modules", name, "package.json"));
|
|
244
|
+
// …or hoisted: npm dedupes, and with an override the fixed Harper may sit
|
|
245
|
+
// at the staging root rather than nest under flair. Check both.
|
|
246
|
+
candidates.push(join(stagingDir, "node_modules", name, "package.json"));
|
|
247
|
+
}
|
|
248
|
+
for (const candidate of candidates) {
|
|
215
249
|
if (existsSync(candidate)) {
|
|
216
250
|
try {
|
|
217
251
|
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
@@ -224,16 +258,20 @@ export function resolveStagedHarperVersion(stagingDir) {
|
|
|
224
258
|
}
|
|
225
259
|
}
|
|
226
260
|
// Last resort: resolve through the staged flair's module graph.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
261
|
+
const flairPkg = join(stagingDir, "node_modules", FLAIR_PKG, "package.json");
|
|
262
|
+
for (const name of HARPER_PKG_NAMES) {
|
|
263
|
+
try {
|
|
264
|
+
const req = createRequire(flairPkg);
|
|
265
|
+
const resolved = req.resolve(`${name}/package.json`);
|
|
266
|
+
const pkg = JSON.parse(readFileSync(resolved, "utf8"));
|
|
267
|
+
if (typeof pkg.version === "string")
|
|
268
|
+
return pkg.version;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
/* try the next package name */
|
|
272
|
+
}
|
|
236
273
|
}
|
|
274
|
+
return null;
|
|
237
275
|
}
|
|
238
276
|
/** Locate the staged @tpsdev-ai/flair package root (the deploy() packageRoot). */
|
|
239
277
|
export function resolveStagedFlairRoot(stagingDir) {
|
package/dist/fleet-verify.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* ── What "peer" means here — read before trusting a green sweep ──────────
|
|
14
14
|
* Harper Fabric's OWN cluster-replication peers (the physical nodes a
|
|
15
15
|
* component gets replicated to by `harper deploy`) are NOT enumerable
|
|
16
|
-
* through any operation in the OSS
|
|
16
|
+
* through any operation in the OSS harper build this repo
|
|
17
17
|
* depends on. `cluster_status` — the operation that would answer "what
|
|
18
18
|
* nodes are in this cluster and are they in sync" — is explicitly called
|
|
19
|
-
* out as harper-pro-only in Harper's own source (node_modules
|
|
20
|
-
*
|
|
19
|
+
* out as harper-pro-only in Harper's own source (node_modules/harper/
|
|
20
|
+
* components/mcp/tools/schemas/operationDescriptions.ts: "Out-of-core
|
|
21
21
|
* operations (e.g. harper-pro's cluster_status) cannot have entries here").
|
|
22
22
|
* There is no "list this cluster's replication nodes" call available to this
|
|
23
23
|
* CLI, on the origin or anywhere else — direct AND via-origin cluster
|
package/dist/lib/mcp-enable.js
CHANGED
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
*
|
|
71
71
|
* ── Ground truth used to design the remote "existing ops paths" step ────────
|
|
72
72
|
* Verified against the ACTUALLY INSTALLED packages, not assumed:
|
|
73
|
-
* -
|
|
73
|
+
* - `harper`'s Operations API has a genuine `set_configuration`
|
|
74
74
|
* operation (writes harperdb-config.yaml for all workers, requires a
|
|
75
|
-
* restart to take effect — node_modules
|
|
75
|
+
* restart to take effect — node_modules/harper/dist/config/
|
|
76
76
|
* configUtils.js's `setConfiguration`) and a genuine `restart` operation
|
|
77
77
|
* (whole-process restart — see .../components/mcp/tools/schemas/
|
|
78
78
|
* operationDescriptions.js's operation catalog). Both are called the
|
package/dist/rem/scheduler.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync, rmSync } from "node:fs";
|
|
17
17
|
import { resolve, dirname } from "node:path";
|
|
18
18
|
import { homedir, platform } from "node:os";
|
|
19
|
-
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
export const SHIM_PATH_DEFAULT = resolve(homedir(), ".flair", "bin", "flair-rem-nightly");
|
|
22
22
|
export const LAUNCHD_PLIST_PATH = resolve(homedir(), "Library", "LaunchAgents", "dev.flair.rem.nightly.plist");
|
|
@@ -102,10 +102,16 @@ function writeFileWithDir(path, contents, mode = 0o600) {
|
|
|
102
102
|
// 30s ceiling on launchctl/systemctl invocations so a hung service manager
|
|
103
103
|
// can't block the CLI indefinitely. Sherlock #415 follow-up.
|
|
104
104
|
const SPAWN_TIMEOUT_MS = 30_000;
|
|
105
|
-
|
|
105
|
+
// Status-check spawns (launchctl print / systemctl is-active) return
|
|
106
|
+
// near-instantly when the service manager is reachable, and fail fast (no
|
|
107
|
+
// hang) when it isn't (e.g. "Failed to connect to bus"). A short ceiling
|
|
108
|
+
// keeps `flair rem nightly status` and the Health endpoint responsive even
|
|
109
|
+
// when the query is inconclusive.
|
|
110
|
+
const STATUS_CHECK_TIMEOUT_MS = 5_000;
|
|
111
|
+
function spawnReport(cmd, timeoutMs = SPAWN_TIMEOUT_MS) {
|
|
106
112
|
const r = spawnSync(cmd[0], cmd.slice(1), {
|
|
107
113
|
encoding: "buffer",
|
|
108
|
-
timeout:
|
|
114
|
+
timeout: timeoutMs,
|
|
109
115
|
});
|
|
110
116
|
return {
|
|
111
117
|
code: r.status,
|
|
@@ -113,6 +119,195 @@ function spawnReport(cmd) {
|
|
|
113
119
|
stderr: r.stderr?.toString("utf-8") ?? "",
|
|
114
120
|
};
|
|
115
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Command to genuinely query the platform scheduler's active/loaded state
|
|
124
|
+
* for a given install (as opposed to the shape of `loadCommand`, which
|
|
125
|
+
* *installs* it). Shared by the sync and async active-state checks below so
|
|
126
|
+
* the two can't drift.
|
|
127
|
+
*/
|
|
128
|
+
function activeCheckCommand(plat) {
|
|
129
|
+
if (plat === "darwin") {
|
|
130
|
+
return ["launchctl", "print", `gui/${process.getuid?.() ?? ""}/dev.flair.rem.nightly`];
|
|
131
|
+
}
|
|
132
|
+
return ["systemctl", "--user", "is-active", "flair-rem-nightly.timer"];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Interprets the result of `activeCheckCommand()`. Shared by the sync
|
|
136
|
+
* (CLI) and async (server) callers.
|
|
137
|
+
*
|
|
138
|
+
* - true — the service manager confirms the job is loaded/active.
|
|
139
|
+
* - false — confirmed NOT active. This includes the "no session bus" case
|
|
140
|
+
* (flair#850): when `systemctl --user` can't reach a bus, it fails
|
|
141
|
+
* before printing a status word ("Failed to connect to bus: No medium
|
|
142
|
+
* found") — but nothing CAN be running without a bus, so `false` is the
|
|
143
|
+
* honest answer, not "unknown".
|
|
144
|
+
* - null — genuinely inconclusive (the command itself couldn't run at
|
|
145
|
+
* all — e.g. the binary is missing — with no output to interpret).
|
|
146
|
+
*/
|
|
147
|
+
export function interpretActiveResult(plat, code, stdout, stderr) {
|
|
148
|
+
const noOutput = !stdout.trim() && !stderr.trim();
|
|
149
|
+
if (plat === "darwin") {
|
|
150
|
+
if (code === 0)
|
|
151
|
+
return true;
|
|
152
|
+
if (code === null && noOutput)
|
|
153
|
+
return null; // spawn itself failed — inconclusive
|
|
154
|
+
return false; // launchctl ran and reported not-loaded
|
|
155
|
+
}
|
|
156
|
+
const out = stdout.trim();
|
|
157
|
+
if (out === "active" || out === "activating")
|
|
158
|
+
return true;
|
|
159
|
+
if (out === "inactive" || out === "failed" || out === "unknown")
|
|
160
|
+
return false;
|
|
161
|
+
if (code === null && noOutput)
|
|
162
|
+
return null; // spawn itself failed — inconclusive
|
|
163
|
+
return false; // covers the no-bus case: empty stdout, nonzero/failed exit
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Synchronous active-state check for CLI use (`flair rem nightly status`).
|
|
167
|
+
* Blocking is fine here — this is a one-shot process and the caller wants
|
|
168
|
+
* the answer before printing anything.
|
|
169
|
+
*/
|
|
170
|
+
function queryActiveState(plat) {
|
|
171
|
+
const [cmd, ...args] = activeCheckCommand(plat);
|
|
172
|
+
const r = spawnReport([cmd, ...args], STATUS_CHECK_TIMEOUT_MS);
|
|
173
|
+
return interpretActiveResult(plat, r.code, r.stdout, r.stderr);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Async, non-blocking equivalent for server contexts (the Health endpoint)
|
|
177
|
+
* where a synchronous subprocess would stall the request-handling thread.
|
|
178
|
+
* Same semantics as `queryActiveState()`.
|
|
179
|
+
*/
|
|
180
|
+
export async function queryActiveStateAsync(plat, timeoutMs = STATUS_CHECK_TIMEOUT_MS) {
|
|
181
|
+
const [cmd, ...args] = activeCheckCommand(plat);
|
|
182
|
+
return new Promise((resolvePromise) => {
|
|
183
|
+
let settled = false;
|
|
184
|
+
let child;
|
|
185
|
+
const finish = (result) => {
|
|
186
|
+
if (settled)
|
|
187
|
+
return;
|
|
188
|
+
settled = true;
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
resolvePromise(result);
|
|
191
|
+
};
|
|
192
|
+
const timer = setTimeout(() => {
|
|
193
|
+
try {
|
|
194
|
+
child?.kill("SIGKILL");
|
|
195
|
+
}
|
|
196
|
+
catch { /* best-effort */ }
|
|
197
|
+
finish(null);
|
|
198
|
+
}, timeoutMs);
|
|
199
|
+
try {
|
|
200
|
+
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
finish(null);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let stdout = "";
|
|
207
|
+
let stderr = "";
|
|
208
|
+
child.stdout?.on("data", (d) => { stdout += d.toString("utf-8"); });
|
|
209
|
+
child.stderr?.on("data", (d) => { stderr += d.toString("utf-8"); });
|
|
210
|
+
child.on("error", () => finish(null));
|
|
211
|
+
child.on("close", (code) => finish(interpretActiveResult(plat, code, stdout, stderr)));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Human remedy text for a failed scheduler-load attempt (flair#850). Covers
|
|
216
|
+
* the one root cause traced so far: a missing systemd user session bus,
|
|
217
|
+
* which blocks `systemctl --user` entirely in ssh-without-lingering,
|
|
218
|
+
* container, and CI contexts. Returns null when the failure doesn't match a
|
|
219
|
+
* known pattern — the caller already prints the raw stderr, so the operator
|
|
220
|
+
* still has something to go on.
|
|
221
|
+
*/
|
|
222
|
+
export function describeLoadFailure(plat, loadResult) {
|
|
223
|
+
const stderr = loadResult.stderr || "";
|
|
224
|
+
if (plat === "linux" && /failed to connect to bus/i.test(stderr)) {
|
|
225
|
+
return ("No systemd user session bus is available in this session (common over ssh without lingering, " +
|
|
226
|
+
"in containers, or under CI). Fix: enable lingering for this user — `loginctl enable-linger <user>` " +
|
|
227
|
+
"— then re-run `flair rem nightly enable`.");
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Formats the `flair rem nightly enable` report from an `EnableResult`.
|
|
233
|
+
* Pulled out of the CLI action so the success-vs-failure decision (flair#850:
|
|
234
|
+
* do not print a success headline before activation is known to have
|
|
235
|
+
* succeeded) is unit-testable without spawning a real launchctl/systemctl or
|
|
236
|
+
* parsing CLI argv.
|
|
237
|
+
*
|
|
238
|
+
* `r.loadResult` is only set when the load command actually ran (the CLI
|
|
239
|
+
* never sets `skipLoad`). A missing `loadResult` (test-only path) is treated
|
|
240
|
+
* as success — matches the CLI's real-world behavior, which always runs the
|
|
241
|
+
* load command and therefore always gets a `loadResult`.
|
|
242
|
+
*/
|
|
243
|
+
export function formatEnableReport(r, input) {
|
|
244
|
+
const { hour, minute, agentId, flairUrl } = input;
|
|
245
|
+
const scheduleTime = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
246
|
+
const activationFailed = !!r.loadResult && r.loadResult.code !== 0;
|
|
247
|
+
if (activationFailed) {
|
|
248
|
+
const lr = r.loadResult;
|
|
249
|
+
const lines = [
|
|
250
|
+
`⚠️ REM nightly scheduler files written but NOT activated (${r.platform})`,
|
|
251
|
+
` Schedule: ${scheduleTime} local time — NOT scheduled (see below)`,
|
|
252
|
+
` Scheduler: ${r.schedulerPath}`,
|
|
253
|
+
` Shim: ${r.shimPath}`,
|
|
254
|
+
` Agent: ${agentId}`,
|
|
255
|
+
` Flair URL: ${flairUrl}`,
|
|
256
|
+
` Activation: ${r.loadCommand.join(" ")} → code ${lr.code}`,
|
|
257
|
+
];
|
|
258
|
+
if (lr.stderr)
|
|
259
|
+
lines.push(` stderr: ${lr.stderr.trim()}`);
|
|
260
|
+
const remedy = describeLoadFailure(r.platform, lr);
|
|
261
|
+
lines.push("");
|
|
262
|
+
lines.push(remedy ? ` ${remedy}` : ` Re-run the activation command above manually to see the full diagnostic.`);
|
|
263
|
+
lines.push("");
|
|
264
|
+
lines.push(` Nothing is scheduled until activation succeeds. Check anytime with: flair rem nightly status`);
|
|
265
|
+
return { lines, ok: false };
|
|
266
|
+
}
|
|
267
|
+
const lines = [
|
|
268
|
+
`✅ REM nightly scheduler enabled (${r.platform})`,
|
|
269
|
+
` Schedule: ${scheduleTime} local time`,
|
|
270
|
+
` Scheduler: ${r.schedulerPath}`,
|
|
271
|
+
` Shim: ${r.shimPath}`,
|
|
272
|
+
` Agent: ${agentId}`,
|
|
273
|
+
` Flair URL: ${flairUrl}`,
|
|
274
|
+
];
|
|
275
|
+
if (r.loadResult) {
|
|
276
|
+
lines.push(` Load: ${r.loadCommand.join(" ")} → ok`);
|
|
277
|
+
}
|
|
278
|
+
lines.push("");
|
|
279
|
+
lines.push(`Tip: run \`flair rem nightly run-once --dry-run\` to verify the cycle works`);
|
|
280
|
+
lines.push(` before the first scheduled fire. Disable with \`flair rem nightly disable\`.`);
|
|
281
|
+
return { lines, ok: true };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Formats the `flair rem nightly status` report from a `SchedulerStatus`.
|
|
285
|
+
* Extracted for the same testability reason as `formatEnableReport()`
|
|
286
|
+
* (flair#850): status must report genuine active state, not file presence.
|
|
287
|
+
*/
|
|
288
|
+
export function formatStatusReport(s) {
|
|
289
|
+
const activeTxt = s.active === true ? "yes" : s.active === false ? "no" : "unknown";
|
|
290
|
+
const lines = [
|
|
291
|
+
`REM nightly scheduler (${s.platform}):`,
|
|
292
|
+
` Active: ${activeTxt}`,
|
|
293
|
+
` Installed: ${s.installed ? "yes" : "no"}`,
|
|
294
|
+
` Scheduler: ${s.schedulerPath}`,
|
|
295
|
+
` Shim: ${s.shimPath}${s.shimExists ? "" : " (missing)"}`,
|
|
296
|
+
];
|
|
297
|
+
if (!s.installed) {
|
|
298
|
+
lines.push("");
|
|
299
|
+
lines.push(`Enable with: flair rem nightly enable --agent <id> [--at HH:MM]`);
|
|
300
|
+
}
|
|
301
|
+
else if (s.active === false) {
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(`Files are written but nothing is scheduled — the job is not loaded/active.`);
|
|
304
|
+
lines.push(`Re-run: flair rem nightly enable --agent <id> [--at HH:MM]`);
|
|
305
|
+
}
|
|
306
|
+
// Status is informational — it does not itself signal process failure,
|
|
307
|
+
// consistent with the pre-existing "not installed" case never exiting
|
|
308
|
+
// nonzero. `ok` here only reflects whether the headline claims success.
|
|
309
|
+
return { lines, ok: s.active !== false };
|
|
310
|
+
}
|
|
116
311
|
/**
|
|
117
312
|
* Installs the platform-native scheduler entry and the shim script.
|
|
118
313
|
*
|
|
@@ -215,26 +410,40 @@ export function disableScheduler(opts = {}) {
|
|
|
215
410
|
return { platform: plat, removed, unloadCommand, unloadResult };
|
|
216
411
|
}
|
|
217
412
|
/**
|
|
218
|
-
* Reports whether the scheduler is installed
|
|
219
|
-
*
|
|
220
|
-
*
|
|
413
|
+
* Reports whether the scheduler is installed AND whether it is genuinely
|
|
414
|
+
* active. File presence alone proves the templates were written — it does
|
|
415
|
+
* NOT prove `launchctl`/`systemctl` successfully loaded the job (flair#850).
|
|
221
416
|
*/
|
|
222
417
|
export function schedulerStatus(opts = {}) {
|
|
223
418
|
const plat = detectPlatform(opts.platformOverride);
|
|
419
|
+
const shimPath = opts.shimPathOverride ?? SHIM_PATH_DEFAULT;
|
|
420
|
+
let schedulerPath;
|
|
421
|
+
let installed;
|
|
224
422
|
if (plat === "darwin") {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
423
|
+
schedulerPath = opts.launchdPlistOverride ?? LAUNCHD_PLIST_PATH;
|
|
424
|
+
installed = existsSync(schedulerPath);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
schedulerPath = opts.systemdTimerOverride ?? SYSTEMD_TIMER_PATH;
|
|
428
|
+
const servicePath = opts.systemdServiceOverride ?? SYSTEMD_SERVICE_PATH;
|
|
429
|
+
installed = existsSync(schedulerPath) && existsSync(servicePath);
|
|
430
|
+
}
|
|
431
|
+
let active;
|
|
432
|
+
if (!installed) {
|
|
433
|
+
active = false; // nothing written — definitely nothing active
|
|
434
|
+
}
|
|
435
|
+
else if (opts.skipActiveCheck) {
|
|
436
|
+
active = null; // caller opted out — unknown, not a claim either way
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
active = queryActiveState(plat);
|
|
232
440
|
}
|
|
233
441
|
return {
|
|
234
442
|
platform: plat,
|
|
235
|
-
installed
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
443
|
+
installed,
|
|
444
|
+
active,
|
|
445
|
+
schedulerPath,
|
|
446
|
+
shimPath,
|
|
447
|
+
shimExists: existsSync(shimPath),
|
|
239
448
|
};
|
|
240
449
|
}
|
package/dist/resources/Admin.js
CHANGED
package/dist/resources/Agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resource, databases } from "
|
|
1
|
+
import { Resource, databases } from "harper";
|
|
2
2
|
import { selectPublicDescription, selectPublicSkills } from "./agentcard-fields.js";
|
|
3
3
|
export class AgentCard extends Resource {
|
|
4
4
|
// Public discovery metadata (per A2A spec, agent cards are intentionally
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Auth: admin only.
|
|
18
18
|
*/
|
|
19
|
-
import { Resource, databases } from "
|
|
19
|
+
import { Resource, databases } from "harper";
|
|
20
20
|
import { isAdmin, allowAdmin } from "./agent-auth.js";
|
|
21
21
|
const DEFAULT_SOUL_KEYS = (agentId, displayName, role, now) => ({
|
|
22
22
|
name: displayName,
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
* validated single-entity + bounded-window condition (WorkspaceState), OR
|
|
76
76
|
* goes through the source's own content-gated resource (Presence).
|
|
77
77
|
*/
|
|
78
|
-
import { Resource, databases } from "
|
|
78
|
+
import { Resource, databases } from "harper";
|
|
79
79
|
import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
|
|
80
80
|
import { isValidEntity } from "./entity-vocab.js";
|
|
81
81
|
import { resolveReadScope } from "./memory-read-scope.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { databases } from "
|
|
1
|
+
import { databases } from "harper";
|
|
2
2
|
import { resolveAgentAuth, allowVerified } from "./agent-auth.js";
|
|
3
3
|
const FORBIDDEN = (msg) => new Response(JSON.stringify({ error: msg }), { status: 403, headers: { "Content-Type": "application/json" } });
|
|
4
4
|
const UNAUTH = () => new Response(JSON.stringify({ error: "authentication required" }), { status: 401, headers: { "Content-Type": "application/json" } });
|
package/dist/resources/Memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { databases } from "
|
|
1
|
+
import { databases } from "harper";
|
|
2
2
|
import { patchRecord, withDetachedTxn } from "./table-helpers.js";
|
|
3
3
|
import { isAdmin, resolveAgentAuth } from "./agent-auth.js";
|
|
4
4
|
import { localInstanceId } from "./instance-identity.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Resource, databases } from "
|
|
1
|
+
import { Resource, databases } from "harper";
|
|
2
2
|
import { allowVerified, resolveAgentAuth } from "./agent-auth.js";
|
|
3
3
|
import { getEmbedding } from "./embeddings-provider.js";
|
|
4
4
|
import { wrapUntrusted } from "./content-safety.js";
|