@avantf/dsh-mem 0.6.1 → 0.6.2

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.
@@ -13,7 +13,7 @@
13
13
  * `ToolSpec.key` is typed `string` (not a literal union), so exhaustiveness cannot be checked at
14
14
  * compile time — the spec assertion is the gate.
15
15
  */
16
- import type { KbAddRequest } from '../mem-contract/index.js';
16
+ import type { KbAddRequest, ToolEnvelope, ToolSpec } from '../mem-contract/index.js';
17
17
  import type { AvantfRuntime } from './runtime.js';
18
18
  /**
19
19
  * The default page for a model-facing `kb_list`.
@@ -49,3 +49,21 @@ export declare function supportsToolKey(key: string): boolean;
49
49
  * both adapters route the throw through their structured error envelope.
50
50
  */
51
51
  export declare function dispatchToolKey(rt: AvantfRuntime, key: string, args: Record<string, unknown>): Promise<unknown>;
52
+ /**
53
+ * The ONE model-facing boundary: validate against the contract spec, dispatch the key, and shape the
54
+ * result into the agent envelope.
55
+ *
56
+ * Every model-facing surface shares this — the DSH tool runner (`@avantf/dsh-mem`) and the MCP
57
+ * server — because the envelope is `ToolEnvelope`, a wire shape: an MCP caller and a DSH caller
58
+ * asking the same question must get the same fields. It used to live only in the plugin, so the MCP
59
+ * surface re-implemented it and the two could drift field by field.
60
+ *
61
+ * What stays in the CALLERS is everything AROUND the envelope: MCP's `textResult`/`isError` wrapper
62
+ * and the plugin's `present` view plus its Remote gateway. The Remote gateway is deliberately NOT
63
+ * routed through here (`mem/DESIGN.md` §239: it keeps `mem_admin`'s retention diagnostics and uses
64
+ * the wire error type, not `toolErr`).
65
+ *
66
+ * A validation failure answers `validationError` (named for the spec), an unknown key answers
67
+ * `toolErr`, and a throwing runtime call answers `toolErr` too — a raw exception never escapes.
68
+ */
69
+ export declare function runToolSpec(rt: AvantfRuntime, spec: ToolSpec, args: unknown): Promise<ToolEnvelope<unknown>>;
@@ -17,6 +17,7 @@ export * from './config/paths.js';
17
17
  export * from './config/loader.js';
18
18
  export * from './modelBootstrap.js';
19
19
  export * from './dispatch.js';
20
+ export * from './trustHeartbeat.js';
20
21
  export * from './runtime.js';
21
22
  export * from './store/memory.js';
22
23
  export * from './store/knowledge.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The trust heartbeat: the interval beat that advances the active-day clock and performs the
3
+ * settle / forget / idle / purge sweeps (TRUST_MODEL.md §5), shared by the DSH plugin and the MCP
4
+ * server.
5
+ *
6
+ * The two surfaces used to install it separately, and the copies drifted in exactly the places a
7
+ * copy drifts: the wording of the failure line and the work each beat does after the tick. The tick
8
+ * itself — the one thing that MUST be identical, because it is the trust clock — is what lives
9
+ * here. The differences are data: {@link TrustHeartbeatOptions.onBeat} is the extra work (the plugin
10
+ * sweeps entities and retries the vector migration), {@link TrustHeartbeatOptions.tickFailed} is how
11
+ * that surface names a failed sweep.
12
+ *
13
+ * `heartbeatMinutes <= 0` is the caller's decision, not this function's: a surface that disables the
14
+ * beat also decides whether it logs the interval line.
15
+ *
16
+ * Zero dependencies beyond `setInterval` so it stays in the engine (the plugin's index and the MCP
17
+ * entry both consume it).
18
+ */
19
+ /** A single beat's worth of the trust clock, plus the two per-surface differences. */
20
+ export interface TrustHeartbeatOptions {
21
+ /** The store whose active-day clock advances on presence. */
22
+ readonly memory: {
23
+ trustTick(): void;
24
+ };
25
+ /** Where a failed sweep is reported. Never allowed to make the beat itself throw. */
26
+ readonly logger: {
27
+ warn(message: string): void;
28
+ };
29
+ /** Minutes between beats. The caller does not install the beat unless this is positive. */
30
+ readonly heartbeatMinutes: number;
31
+ /** Extra per-beat work, run after the tick and OUTSIDE its catch (a failure here is the caller's). */
32
+ readonly onBeat?: () => void;
33
+ /**
34
+ * How this surface names a failed tick, given the caught value's message. Optional so the default
35
+ * (`trust tick failed: …`) holds for a caller that has no opinion.
36
+ */
37
+ readonly tickFailed?: (message: string) => string;
38
+ }
39
+ /**
40
+ * Install the heartbeat and return its disposer.
41
+ *
42
+ * A `trustTick` that throws is warned about and swallowed: a failed sweep must never take the host
43
+ * or the server down, and it must not be invisible either — a trust clock that stopped advancing
44
+ * shows up much later as facts that never settle, forget or purge. The timer is `unref`'d where the
45
+ * runtime supports it, so it never holds a process open by itself.
46
+ */
47
+ export declare function installTrustHeartbeat(options: TrustHeartbeatOptions): () => void;
package/lib/index.js CHANGED
@@ -1325,6 +1325,170 @@ function promptTextWarnings(sections) {
1325
1325
  return out;
1326
1326
  }
1327
1327
  //#endregion
1328
+ //#region lib/types/interface_gate.js
1329
+ /** Read one member without letting a hostile module's getter escape. */
1330
+ function member(module, name) {
1331
+ try {
1332
+ return module?.[name];
1333
+ } catch {
1334
+ return;
1335
+ }
1336
+ }
1337
+ function cannotTell(reason) {
1338
+ return {
1339
+ status: "cannot-tell",
1340
+ reason
1341
+ };
1342
+ }
1343
+ /**
1344
+ * Whether the base's shared capabilities may be used under `verdict`.
1345
+ *
1346
+ * The ONE mapping from the gate's verdict to the loader's decision, exported so it is asserted
1347
+ * directly and the loader and its test cannot drift: only `incompatible` withholds the base;
1348
+ * `cannot-tell` does not, because it is not a negative answer. A `false` here is still a DEGRADATION
1349
+ * (own prompt defaults, gate skipped, legacy provisioning), never a refused mount.
1350
+ */
1351
+ function baseIsUsable(verdict) {
1352
+ return verdict.status !== "incompatible";
1353
+ }
1354
+ /**
1355
+ * Where the build's bake record sits when no explicit URL is given: beside the built ENTRY
1356
+ * (`lib/index.js`), which is where `scripts/link-envinit.mjs` writes it. A per-file `tsc` emit of
1357
+ * this module lives one directory below that entry and looks one level up; the first readable record
1358
+ * wins. Under `vitest` (running from `src/`) neither exists, which is the correct "not baked".
1359
+ */
1360
+ const BAKED_CANDIDATES = ["./interface-version.json", "../interface-version.json"];
1361
+ /**
1362
+ * Read this artifact's baked requirement through the loaded base's own reader.
1363
+ * @param read - the base's `readInterfaceRequirement`, taken off the loaded module.
1364
+ * @param bakedUrl - test seam: read EXACTLY this record instead of walking {@link BAKED_CANDIDATES}.
1365
+ */
1366
+ function requirementOf(read, bakedUrl) {
1367
+ const candidates = bakedUrl === void 0 ? BAKED_CANDIDATES.map((relative) => new URL(relative, import.meta.url)) : [bakedUrl];
1368
+ for (const url of candidates) {
1369
+ let requirement;
1370
+ try {
1371
+ requirement = read(url);
1372
+ } catch {
1373
+ continue;
1374
+ }
1375
+ const version = requirement?.interfaceVersion;
1376
+ if (typeof version === "number" && Number.isInteger(version) && version > 0) return { interfaceVersion: version };
1377
+ }
1378
+ }
1379
+ /**
1380
+ * Ask the loaded base whether it implements the interface generation this artifact was built for.
1381
+ *
1382
+ * @param module - the loaded base module (only `checkInterface` / `readInterfaceRequirement` are
1383
+ * touched, each through a guarded read).
1384
+ * @param bakedUrl - test seam: the bake record to read; production leaves it undefined.
1385
+ * @returns a verdict that is never `incompatible` unless BOTH sides were readable and differ, and
1386
+ * never a throw.
1387
+ */
1388
+ function interfaceVerdict(module, bakedUrl) {
1389
+ const check = member(module, "checkInterface");
1390
+ const read = member(module, "readInterfaceRequirement");
1391
+ if (typeof check !== "function" || typeof read !== "function") return cannotTell("the loaded base has no interface gate (checkInterface / readInterfaceRequirement missing) — it predates the runtime interface contract, so which generation it implements cannot be told");
1392
+ const requirement = requirementOf(read, bakedUrl);
1393
+ if (requirement === void 0) return cannotTell("this build has no baked interface requirement (lib/interface-version.json is missing or malformed), so the runtime interface gate cannot run");
1394
+ let verdict;
1395
+ try {
1396
+ verdict = check(requirement.interfaceVersion, module);
1397
+ } catch {
1398
+ return cannotTell("the loaded base threw while judging the interface generation");
1399
+ }
1400
+ const status = verdict?.status;
1401
+ if (status !== "ok" && status !== "incompatible" && status !== "cannot-tell") return cannotTell("the loaded base returned no usable interface verdict");
1402
+ return verdict;
1403
+ }
1404
+ /** Emit a warning without ever letting the logger turn it into a crash. */
1405
+ function warn$1(log, message) {
1406
+ try {
1407
+ log?.warn(message);
1408
+ } catch {}
1409
+ }
1410
+ function reasonOf$1(error) {
1411
+ return error instanceof Error ? error.message : String(error);
1412
+ }
1413
+ /**
1414
+ * Run the runtime interface gate and answer whether the loaded base's shared capabilities may be
1415
+ * used.
1416
+ *
1417
+ * `false` is a DEGRADATION the caller answers by taking its own full-degradation route (no runtime,
1418
+ * own prompt defaults, the compatibility gate skipped, legacy provisioning) — never a refused mount,
1419
+ * and never a throw. `cannot-tell` warns and answers `true`: "cannot tell" is not a negative answer,
1420
+ * so the base is used normally. The verdict's own reason is the base's; only the surrounding sentence
1421
+ * is the caller's ({@link DegradeWords}).
1422
+ */
1423
+ function gateOrDegrade(module, log, words) {
1424
+ const verdict = interfaceVerdict(module);
1425
+ if (!baseIsUsable(verdict)) {
1426
+ warn$1(log, `${words.prefix} WARNING — interface: ${verdict.reason ?? "the loaded base implements another interface generation"}; the base's shared capabilities are NOT used (${words.withheld}) and the plugin mounts anyway`);
1427
+ return false;
1428
+ }
1429
+ if (verdict.status === "cannot-tell") warn$1(log, `${words.prefix} WARNING — interface: ${verdict.reason ?? "the interface generation cannot be told"}; using the loaded base anyway ("cannot tell" is never "incompatible")`);
1430
+ return true;
1431
+ }
1432
+ /**
1433
+ * The guard that makes a loader's "never throws" true: every failure is one warning plus
1434
+ * `undefined`. The caller owns the sentence's tail (`words`) and its own cache discipline — a
1435
+ * FAILED load is not cached, so this must return `undefined` rather than reject (see
1436
+ * {@link createLoadCache}).
1437
+ */
1438
+ async function loadGuarded(options, loadOnce, log, words) {
1439
+ try {
1440
+ return await loadOnce(options);
1441
+ } catch (error) {
1442
+ warn$1(log, `${words.prefix} WARNING — environment initialisation failed (${reasonOf$1(error)}); ${words.loadFailure}`);
1443
+ return;
1444
+ }
1445
+ }
1446
+ /**
1447
+ * Wrap a guarded loader in the cache discipline both startups share: the first call runs it, every
1448
+ * later and concurrent call shares its result, and a FAILED load is NOT cached. A failure is "cannot
1449
+ * tell", not a verdict — and it may be transient (offline at boot, a half-written install) — so a
1450
+ * profile reload, or a second instance in the same process, retries instead of replaying the first
1451
+ * failure for the whole process lifetime.
1452
+ */
1453
+ function createLoadCache(guarded) {
1454
+ let loaded;
1455
+ let loading;
1456
+ return {
1457
+ load(options) {
1458
+ if (loaded !== void 0) return Promise.resolve(loaded);
1459
+ if (loading !== void 0) return loading;
1460
+ loading = guarded(options).then((runtime) => {
1461
+ if (runtime === void 0) loading = void 0;
1462
+ else loaded = runtime;
1463
+ return runtime;
1464
+ }, () => {
1465
+ loading = void 0;
1466
+ });
1467
+ return loading;
1468
+ },
1469
+ clear() {
1470
+ loaded = void 0;
1471
+ loading = void 0;
1472
+ }
1473
+ };
1474
+ }
1475
+ /**
1476
+ * Whether a Cordis context is still alive.
1477
+ *
1478
+ * Cordis clears `fiber.uid` on disposal, and every later context call then throws
1479
+ * `INACTIVE_EFFECT`. `apply` awaits environment preparation, so a profile reload or unload can land
1480
+ * inside that window and half-register a plugin (the Remote face cannot be rolled back). A fiberless
1481
+ * stub counts as active: an unknown shape is "cannot tell", never "disposed".
1482
+ *
1483
+ * Generic Cordis liveness rather than interface-gate knowledge — but the two plugins' startup paths
1484
+ * share it byte-for-byte, so it lives with their one shared shim, where the two readings of "is my
1485
+ * context alive" cannot drift.
1486
+ */
1487
+ function stillActive(ctx) {
1488
+ const fiber = ctx?.fiber;
1489
+ return fiber === void 0 || fiber.uid !== null;
1490
+ }
1491
+ //#endregion
1328
1492
  //#region lib/types/hints.js
1329
1493
  /**
1330
1494
  * The plugin's CONDITIONAL hint: ONE line, contributed only when either store already holds
@@ -3688,6 +3852,79 @@ async function dispatchToolKey(rt, key, args) {
3688
3852
  if (dispatch === void 0) throw new Error(`unknown tool key ${key}`);
3689
3853
  return dispatch(rt, args);
3690
3854
  }
3855
+ /**
3856
+ * The ONE model-facing boundary: validate against the contract spec, dispatch the key, and shape the
3857
+ * result into the agent envelope.
3858
+ *
3859
+ * Every model-facing surface shares this — the DSH tool runner (`@avantf/dsh-mem`) and the MCP
3860
+ * server — because the envelope is `ToolEnvelope`, a wire shape: an MCP caller and a DSH caller
3861
+ * asking the same question must get the same fields. It used to live only in the plugin, so the MCP
3862
+ * surface re-implemented it and the two could drift field by field.
3863
+ *
3864
+ * What stays in the CALLERS is everything AROUND the envelope: MCP's `textResult`/`isError` wrapper
3865
+ * and the plugin's `present` view plus its Remote gateway. The Remote gateway is deliberately NOT
3866
+ * routed through here (`mem/DESIGN.md` §239: it keeps `mem_admin`'s retention diagnostics and uses
3867
+ * the wire error type, not `toolErr`).
3868
+ *
3869
+ * A validation failure answers `validationError` (named for the spec), an unknown key answers
3870
+ * `toolErr`, and a throwing runtime call answers `toolErr` too — a raw exception never escapes.
3871
+ */
3872
+ async function runToolSpec$1(rt, spec, args) {
3873
+ const parsed = spec.input.safeParse(args ?? {});
3874
+ if (!parsed.success) return validationError(spec.name, parsed.error.issues);
3875
+ if (!supportsToolKey(spec.key)) return toolErr(`unknown tool ${spec.key}`);
3876
+ try {
3877
+ const value = await dispatchToolKey(rt, spec.key, parsed.data);
3878
+ return toolOk(modelFacingToolResult(spec.key, parsed.data.action, value));
3879
+ } catch (error) {
3880
+ return toolErr(error);
3881
+ }
3882
+ }
3883
+ //#endregion
3884
+ //#region ../core/lib/trustHeartbeat.js
3885
+ /**
3886
+ * The trust heartbeat: the interval beat that advances the active-day clock and performs the
3887
+ * settle / forget / idle / purge sweeps (TRUST_MODEL.md §5), shared by the DSH plugin and the MCP
3888
+ * server.
3889
+ *
3890
+ * The two surfaces used to install it separately, and the copies drifted in exactly the places a
3891
+ * copy drifts: the wording of the failure line and the work each beat does after the tick. The tick
3892
+ * itself — the one thing that MUST be identical, because it is the trust clock — is what lives
3893
+ * here. The differences are data: {@link TrustHeartbeatOptions.onBeat} is the extra work (the plugin
3894
+ * sweeps entities and retries the vector migration), {@link TrustHeartbeatOptions.tickFailed} is how
3895
+ * that surface names a failed sweep.
3896
+ *
3897
+ * `heartbeatMinutes <= 0` is the caller's decision, not this function's: a surface that disables the
3898
+ * beat also decides whether it logs the interval line.
3899
+ *
3900
+ * Zero dependencies beyond `setInterval` so it stays in the engine (the plugin's index and the MCP
3901
+ * entry both consume it).
3902
+ */
3903
+ /** The generic one-line reason for a caught value. */
3904
+ function messageOf$1(error) {
3905
+ return error instanceof Error ? error.message : String(error);
3906
+ }
3907
+ /**
3908
+ * Install the heartbeat and return its disposer.
3909
+ *
3910
+ * A `trustTick` that throws is warned about and swallowed: a failed sweep must never take the host
3911
+ * or the server down, and it must not be invisible either — a trust clock that stopped advancing
3912
+ * shows up much later as facts that never settle, forget or purge. The timer is `unref`'d where the
3913
+ * runtime supports it, so it never holds a process open by itself.
3914
+ */
3915
+ function installTrustHeartbeat(options) {
3916
+ const timer = setInterval(() => {
3917
+ try {
3918
+ options.memory.trustTick();
3919
+ } catch (error) {
3920
+ options.logger.warn((options.tickFailed ?? ((message) => `trust tick failed: ${message}`))(messageOf$1(error)));
3921
+ }
3922
+ options.onBeat?.();
3923
+ }, options.heartbeatMinutes * 6e4);
3924
+ const unref = timer.unref;
3925
+ if (typeof unref === "function") unref.call(timer);
3926
+ return () => clearInterval(timer);
3927
+ }
3691
3928
  //#endregion
3692
3929
  //#region ../retrieval-core/lib/representation.js
3693
3930
  /**
@@ -16419,6 +16656,12 @@ function cannotDetermine(error, log, prefix) {
16419
16656
  * Run the gate. Call this FIRST in `apply()`: on a refusal nothing has been registered yet, so
16420
16657
  * "do not load" costs nothing to unwind. Never throws — not even with a throwing logger, because
16421
16658
  * "cannot tell" is not "incompatible".
16659
+ *
16660
+ * The check itself is the BASE's `provision` (gather + verdict + logging, one call). The local
16661
+ * fallback stays on purpose (option A): the interface-generation axis cannot gate this dependency
16662
+ * (same-generation bases never read as `incompatible`), and raising the peer floor would degrade
16663
+ * every host on a base that predates the fix — so a `provision` that cannot be performed still
16664
+ * leaves the plugin loading.
16422
16665
  * @param ctx - the plugin's context.
16423
16666
  * @param log - the plugin's logger.
16424
16667
  * @param compat - the loaded gate runtime (see `src/envinit.ts`).
@@ -16427,22 +16670,11 @@ function cannotDetermine(error, log, prefix) {
16427
16670
  * @returns the verdict `apply` branches on.
16428
16671
  */
16429
16672
  function provision(ctx, log, compat, spec = compat.spec) {
16430
- let evidence;
16431
- try {
16432
- evidence = compat.module.gatherEvidence(ctx, spec);
16433
- } catch (error) {
16434
- return cannotDetermine(error, log, compat.prefix);
16435
- }
16436
- let verdict;
16437
16673
  try {
16438
- verdict = compat.module.verdictOf(evidence);
16674
+ return { verdict: compat.module.provision(ctx, log, spec) };
16439
16675
  } catch (error) {
16440
16676
  return cannotDetermine(error, log, compat.prefix);
16441
16677
  }
16442
- for (const line of verdict.lines) try {
16443
- log[line.level](line.message);
16444
- } catch {}
16445
- return { verdict };
16446
16678
  }
16447
16679
  /**
16448
16680
  * Keep one `/mem` command that reports a refusal.
@@ -16888,83 +17120,6 @@ function rangeOf(value) {
16888
17120
  return typeof range === "string" && range.trim() !== "" ? range : void 0;
16889
17121
  }
16890
17122
  //#endregion
16891
- //#region lib/types/interface_gate.js
16892
- /** Read one member without letting a hostile module's getter escape. */
16893
- function member(module, name) {
16894
- try {
16895
- return module?.[name];
16896
- } catch {
16897
- return;
16898
- }
16899
- }
16900
- function cannotTell(reason) {
16901
- return {
16902
- status: "cannot-tell",
16903
- reason
16904
- };
16905
- }
16906
- /**
16907
- * Whether the base's shared capabilities may be used under `verdict`.
16908
- *
16909
- * The ONE mapping from the gate's verdict to the loader's decision, exported so it is asserted
16910
- * directly and the loader and its test cannot drift: only `incompatible` withholds the base;
16911
- * `cannot-tell` does not, because it is not a negative answer. A `false` here is still a DEGRADATION
16912
- * (own prompt defaults, gate skipped, legacy provisioning), never a refused mount.
16913
- */
16914
- function baseIsUsable(verdict) {
16915
- return verdict.status !== "incompatible";
16916
- }
16917
- /**
16918
- * Where the build's bake record sits when no explicit URL is given: beside the built ENTRY
16919
- * (`lib/index.js`), which is where `scripts/link-envinit.mjs` writes it. A per-file `tsc` emit of
16920
- * this module lives one directory below that entry and looks one level up; the first readable record
16921
- * wins. Under `vitest` (running from `src/`) neither exists, which is the correct "not baked".
16922
- */
16923
- const BAKED_CANDIDATES = ["./interface-version.json", "../interface-version.json"];
16924
- /**
16925
- * Read this artifact's baked requirement through the loaded base's own reader.
16926
- * @param read - the base's `readInterfaceRequirement`, taken off the loaded module.
16927
- * @param bakedUrl - test seam: read EXACTLY this record instead of walking {@link BAKED_CANDIDATES}.
16928
- */
16929
- function requirementOf(read, bakedUrl) {
16930
- const candidates = bakedUrl === void 0 ? BAKED_CANDIDATES.map((relative) => new URL(relative, import.meta.url)) : [bakedUrl];
16931
- for (const url of candidates) {
16932
- let requirement;
16933
- try {
16934
- requirement = read(url);
16935
- } catch {
16936
- continue;
16937
- }
16938
- const version = requirement?.interfaceVersion;
16939
- if (typeof version === "number" && Number.isInteger(version) && version > 0) return { interfaceVersion: version };
16940
- }
16941
- }
16942
- /**
16943
- * Ask the loaded base whether it implements the interface generation this artifact was built for.
16944
- *
16945
- * @param module - the loaded base module (only `checkInterface` / `readInterfaceRequirement` are
16946
- * touched, each through a guarded read).
16947
- * @param bakedUrl - test seam: the bake record to read; production leaves it undefined.
16948
- * @returns a verdict that is never `incompatible` unless BOTH sides were readable and differ, and
16949
- * never a throw.
16950
- */
16951
- function interfaceVerdict(module, bakedUrl) {
16952
- const check = member(module, "checkInterface");
16953
- const read = member(module, "readInterfaceRequirement");
16954
- if (typeof check !== "function" || typeof read !== "function") return cannotTell("the loaded base has no interface gate (checkInterface / readInterfaceRequirement missing) — it predates the runtime interface contract, so which generation it implements cannot be told");
16955
- const requirement = requirementOf(read, bakedUrl);
16956
- if (requirement === void 0) return cannotTell("this build has no baked interface requirement (lib/interface-version.json is missing or malformed), so the runtime interface gate cannot run");
16957
- let verdict;
16958
- try {
16959
- verdict = check(requirement.interfaceVersion, module);
16960
- } catch {
16961
- return cannotTell("the loaded base threw while judging the interface generation");
16962
- }
16963
- const status = verdict?.status;
16964
- if (status !== "ok" && status !== "incompatible" && status !== "cannot-tell") return cannotTell("the loaded base returned no usable interface verdict");
16965
- return verdict;
16966
- }
16967
- //#endregion
16968
17123
  //#region lib/types/envinit.js
16969
17124
  /**
16970
17125
  * Environment initialisation for the memory plugin, through the family base
@@ -17224,8 +17379,22 @@ function modelItem(framework, model) {
17224
17379
  policy: { timeoutMs: MODEL_REQUEST_TIMEOUT_MS }
17225
17380
  };
17226
17381
  }
17227
- let loaded;
17228
- let loading;
17382
+ /**
17383
+ * The plugin-specific halves of the shared loader's sentences: mem says the FRAMEWORK is skipped and
17384
+ * names the legacy provisioning path among the withheld capabilities. The mechanism is the shim's
17385
+ * (`interface_gate.ts`), the wording is this plugin's.
17386
+ */
17387
+ const DEGRADE_WORDS = {
17388
+ prefix: OWN_PREFIX,
17389
+ loadFailure: "the framework is SKIPPED and the plugin will mount anyway",
17390
+ withheld: "own prompt defaults, gate skipped, legacy provisioning"
17391
+ };
17392
+ /**
17393
+ * The loaded runtime, cached once per process through the shared cache discipline: a FAILED load is
17394
+ * NOT cached, so a profile reload retries instead of replaying the first failure for the whole
17395
+ * process lifetime.
17396
+ */
17397
+ const cache = createLoadCache((options) => loadGuarded(options, loadOnce, options.log, DEGRADE_WORDS));
17229
17398
  /**
17230
17399
  * Ensure the framework, then the compatibility base, then dispatch the expensive resources.
17231
17400
  *
@@ -17235,32 +17404,11 @@ let loading;
17235
17404
  * base still returns a runtime, with `compat: undefined`: the mount must continue, and the resource
17236
17405
  * items are still worth dispatching.
17237
17406
  *
17238
- * The result is cached per process; a FAILED load is not, so a profile reload retries instead of
17239
- * replaying the first failure for the whole process lifetime.
17240
- *
17241
17407
  * @param options - logger, family root, download policy and the test seams.
17242
17408
  * @returns the loaded runtime, or `undefined` when the framework itself is unavailable.
17243
17409
  */
17244
- async function loadEnvinit(options = {}) {
17245
- if (loaded !== void 0) return loaded;
17246
- if (loading !== void 0) return loading;
17247
- loading = loadGuarded(options).then((runtime) => {
17248
- if (runtime === void 0) loading = void 0;
17249
- else loaded = runtime;
17250
- return runtime;
17251
- }, () => {
17252
- loading = void 0;
17253
- });
17254
- return loading;
17255
- }
17256
- /** The guard that makes "never throws" true: every failure is a warning plus `undefined`. */
17257
- async function loadGuarded(options) {
17258
- try {
17259
- return await loadOnce(options);
17260
- } catch (error) {
17261
- warn(options.log, `${OWN_PREFIX} WARNING — environment initialisation failed (${reasonOf(error)}); the framework is SKIPPED and the plugin will mount anyway`);
17262
- return;
17263
- }
17410
+ function loadEnvinit(options = {}) {
17411
+ return cache.load(options);
17264
17412
  }
17265
17413
  /** The body of {@link loadEnvinit}; the cache above makes it run at most once per process. */
17266
17414
  async function loadOnce(options) {
@@ -17280,14 +17428,7 @@ async function loadOnce(options) {
17280
17428
  warn(options.log, `${OWN_PREFIX} WARNING — ${FRAMEWORK_PACKAGE} could not be made available; the plugin falls back to its own provisioning and mounts anyway`);
17281
17429
  return;
17282
17430
  }
17283
- {
17284
- const verdict = interfaceVerdict(framework);
17285
- if (!baseIsUsable(verdict)) {
17286
- warn(options.log, `${OWN_PREFIX} WARNING — interface: ${verdict.reason ?? "the loaded base implements another interface generation"}; the base's shared capabilities are NOT used (own prompt defaults, gate skipped, legacy provisioning) and the plugin mounts anyway`);
17287
- return;
17288
- }
17289
- if (verdict.status === "cannot-tell") warn(options.log, `${OWN_PREFIX} WARNING — interface: ${verdict.reason ?? "the interface generation cannot be told"}; using the loaded base anyway ("cannot tell" is never "incompatible")`);
17290
- }
17431
+ if (!gateOrDegrade(framework, options.log, DEGRADE_WORDS)) return void 0;
17291
17432
  const disposers = [];
17292
17433
  let compat;
17293
17434
  try {
@@ -17347,8 +17488,7 @@ async function loadOnce(options) {
17347
17488
  for (const instance of disposers.splice(0)) try {
17348
17489
  instance.dispose();
17349
17490
  } catch {}
17350
- loaded = void 0;
17351
- loading = void 0;
17491
+ cache.clear();
17352
17492
  }
17353
17493
  };
17354
17494
  }
@@ -17899,15 +18039,7 @@ const ENTITY_SWEEP_PER_HEARTBEAT = 2e3;
17899
18039
  /** Minimum spacing between corpus drift checks (`tools/result` fires for EVERY tool). */
17900
18040
  const RECONCILE_MIN_INTERVAL_MS = 2e3;
17901
18041
  async function runToolSpec(spec, rt, args) {
17902
- const parsed = spec.input.safeParse(args ?? {});
17903
- if (!parsed.success) return validationError(spec.name, parsed.error.issues);
17904
- if (!supportsToolKey(spec.key)) return toolErr(`unknown tool ${spec.key}`);
17905
- try {
17906
- const value = await dispatchToolKey(rt, spec.key, parsed.data);
17907
- return toolOk(modelFacingToolResult(spec.key, parsed.data.action, value));
17908
- } catch (error) {
17909
- return toolErr(error);
17910
- }
18042
+ return await runToolSpec$1(rt, spec, args);
17911
18043
  }
17912
18044
  function present(spec, args) {
17913
18045
  return {
@@ -18261,19 +18393,6 @@ function hostLogger(ctx) {
18261
18393
  };
18262
18394
  }
18263
18395
  /**
18264
- * Whether this plugin's fiber is still alive after the environment await.
18265
- *
18266
- * The environment gate can take the whole startup budget (15 s on a first run) and the framework may
18267
- * even fetch the gate's base during it. A profile reload or unload landing inside that window clears
18268
- * the fiber's uid, after which every Cordis call throws `INACTIVE_EFFECT` — the plugin would then
18269
- * half-register (the Remote face cannot be rolled back) instead of stopping cleanly. An unknown
18270
- * shape is "cannot tell", so only an explicitly cleared uid counts as disposed.
18271
- */
18272
- function stillActive(ctx) {
18273
- const fiber = ctx.fiber;
18274
- return fiber === void 0 || fiber.uid !== null;
18275
- }
18276
- /**
18277
18396
  * Declare the expensive resources through the family framework, warm the tokenizer and the embedding
18278
18397
  * model, and continue initialisation as each item settles.
18279
18398
  *
@@ -18429,19 +18548,18 @@ async function apply(ctx, config) {
18429
18548
  });
18430
18549
  const heartbeatMinutes = rt.config.common.trust.presence.heartbeat_minutes;
18431
18550
  if (heartbeatMinutes > 0) ctx.effect(() => {
18432
- const timer = setInterval(() => {
18433
- try {
18434
- rt.memory.trustTick();
18435
- } catch (error) {
18436
- logger.warn(`trust tick failed: ${error instanceof Error ? error.message : String(error)}`);
18437
- }
18438
- sweepEntities();
18439
- vectorMigration.start();
18440
- }, heartbeatMinutes * 6e4);
18441
- const unref = timer.unref;
18442
- if (typeof unref === "function") unref.call(timer);
18551
+ const stop = installTrustHeartbeat({
18552
+ memory: rt.memory,
18553
+ logger,
18554
+ heartbeatMinutes,
18555
+ onBeat: () => {
18556
+ sweepEntities();
18557
+ vectorMigration.start();
18558
+ },
18559
+ tickFailed: (message) => `trust tick failed: ${message}`
18560
+ });
18443
18561
  logger.info(`trust heartbeat: every ${heartbeatMinutes}min (active-day clock + lifecycle sweep)`);
18444
- return () => clearInterval(timer);
18562
+ return stop;
18445
18563
  });
18446
18564
  const promptDir = join(rt.config.home, "prompts");
18447
18565
  const kit = env?.kit;
@@ -1,4 +1,4 @@
1
1
  {
2
- "baseVersion": "0.4.0",
2
+ "baseVersion": "0.4.1",
3
3
  "interfaceVersion": 3
4
4
  }
@@ -3,7 +3,7 @@
3
3
  * @module bootstrap
4
4
  */
5
5
  /** The bootstrap version; the resolved framework must satisfy {@link supportedRange}. */
6
- export declare const VERSION = "0.4.0";
6
+ export declare const VERSION = "0.4.1";
7
7
  /**
8
8
  * The framework interval this bootstrap can launch.
9
9
  *
@@ -123,9 +123,6 @@ interface EnvinitLoadOptions {
123
123
  * base still returns a runtime, with `compat: undefined`: the mount must continue, and the resource
124
124
  * items are still worth dispatching.
125
125
  *
126
- * The result is cached per process; a FAILED load is not, so a profile reload retries instead of
127
- * replaying the first failure for the whole process lifetime.
128
- *
129
126
  * @param options - logger, family root, download policy and the test seams.
130
127
  * @returns the loaded runtime, or `undefined` when the framework itself is unavailable.
131
128
  */
@@ -1 +1 @@
1
- {"version":3,"file":"envinit.d.ts","sourceRoot":"","sources":["../../src/envinit.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAiB,oBAAoB,EAAe,MAAM,yBAAyB,CAAA;AAG/F,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAKvB,4FAA4F;AAC5F,KAAK,aAAa,GAAG,cAAc,yBAAyB,CAAC,CAAA;AAK7D;;;GAGG;AACH,eAAO,MAAM,WAAW,eAAe,CAAA;AACvC,eAAO,MAAM,UAAU,cAAc,CAAA;AAmDrC,2EAA2E;AAC3E,UAAU,aAAa;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EACV;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACvF,SAAS,CAAA;CACd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE;IACR,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,EACD,UAAU,EAAE,MAAM,GACjB,YAAY,CAAC,OAAO,CAAC,CAQvB;AAOD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAAA;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAA;IAC1C,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE;;;;;;OAMG;IACH,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI,CAAA;IAC9F,wEAAwE;IACxE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,uCAAuC;AACvC,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CACrC;AA6HD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAkBvG"}
1
+ {"version":3,"file":"envinit.d.ts","sourceRoot":"","sources":["../../src/envinit.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAiB,oBAAoB,EAAe,MAAM,yBAAyB,CAAA;AAG/F,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAA;AAKvB,4FAA4F;AAC5F,KAAK,aAAa,GAAG,cAAc,yBAAyB,CAAC,CAAA;AAK7D;;;GAGG;AACH,eAAO,MAAM,WAAW,eAAe,CAAA;AACvC,eAAO,MAAM,UAAU,cAAc,CAAA;AAmDrC,2EAA2E;AAC3E,UAAU,aAAa;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EACV;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GACvF,SAAS,CAAA;CACd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE;IACR,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,EACD,UAAU,EAAE,MAAM,GACjB,YAAY,CAAC,OAAO,CAAC,CAQvB;AAOD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAAA;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAA;IAC1C,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE;;;;;;OAMG;IACH,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,IAAI,CAAA;IAC9F,wEAAwE;IACxE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,uCAAuC;AACvC,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAA;IAC5B,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,mHAAmH;IACnH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CACrC;AA8ID;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAEjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAQlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,EAAE,mBAAmB,EAAU,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAeL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,YAAY,EACjB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAG1B,MAAM,sBAAsB,CAAA;AAqB7B,eAAO,MAAM,IAAI,eAAe,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAQ7E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AACD,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAO3B,CAAA;AA6CF,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAOvC;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;gBACtB,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa;IAK3C;;;;;;;;;OASG;YACW,IAAI;IAclB;;;;OAIG;IAEH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAMlF,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAMnF,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAKhF;;;;;OAKG;IAEH,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAOpD,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAI5D;;;;OAIG;IAEG,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC;IAUnF,qFAAqF;IAE/E,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAUvE;;;;OAIG;IAEG,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC;IAQvG;;;;OAIG;IAEG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAUpG;;;;;OAKG;IAEG,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;CAiBpE;AAiID,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwYvE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AASlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,EAAE,mBAAmB,EAAU,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EASL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAaL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,YAAY,EACjB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAG1B,MAAM,sBAAsB,CAAA;AAqB7B,eAAO,MAAM,IAAI,eAAe,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAQ7E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AACD,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAO3B,CAAA;AAkCF,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAOvC;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;gBACtB,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa;IAK3C;;;;;;;;;OASG;YACW,IAAI;IAclB;;;;OAIG;IAEH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAMlF,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAMnF,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAKhF;;;;;OAKG;IAEH,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAOpD,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAI5D;;;;OAIG;IAEG,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC;IAUnF,qFAAqF;IAE/E,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAUvE;;;;OAIG;IAEG,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC;IAQvG;;;;OAIG;IAEG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAUpG;;;;;OAKG;IAEG,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;CAiBpE;AAmHD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyYvE"}
@@ -20,6 +20,13 @@
20
20
  * `envinit.ts`, never here. `base/plugin-base/test/interface_consumers.spec.ts` pins the two copies
21
21
  * against each other, so a divergence is a red test instead of two shims that drifted.
22
22
  *
23
+ * The loader SKELETON the two startups share lives here too: the once-per-process cache
24
+ * ({@link createLoadCache}), the guard that turns any throw into one warning
25
+ * ({@link loadGuarded}), and the gate→decision step ({@link gateOrDegrade}). Those sentences are
26
+ * observably NOT the same in the two plugins, so every shared function takes them as
27
+ * {@link DegradeWords}: the shim owns the mechanism, the caller owns the wording, and the two trees'
28
+ * `envinit.spec.ts` pin each plugin's own text verbatim.
29
+ *
23
30
  * @module @avantf/dsh-mem/interface_gate
24
31
  */
25
32
  import type { InterfaceVerdict } from '@avantf/dsh-plugin-base';
@@ -48,4 +55,67 @@ export declare function baseIsUsable(verdict: InterfaceVerdict): boolean;
48
55
  * never a throw.
49
56
  */
50
57
  export declare function interfaceVerdict(module: unknown, bakedUrl?: URL | string): InterfaceVerdict;
58
+ /** The logging surface the shared loader helpers need; both plugins' loggers satisfy it. */
59
+ export interface GateLogger {
60
+ warn(message: string): void;
61
+ }
62
+ /**
63
+ * The two sentences the shared loader may NOT own, because the plugins word them differently: mem
64
+ * says the FRAMEWORK is skipped and names legacy provisioning among the withheld capabilities,
65
+ * mission says the COMPATIBILITY GATE is skipped and names none. Neither is drift — both are read by
66
+ * an operator — so the caller passes them in and the shared code interpolates them verbatim.
67
+ */
68
+ export interface DegradeWords {
69
+ /** This module's warning token, so one grep finds every startup line: `envinit:` / `compat:`. */
70
+ readonly prefix: string;
71
+ /** The tail after "environment initialisation failed (...); " in the guarded-load failure line. */
72
+ readonly loadFailure: string;
73
+ /** The parenthetical after "the base's shared capabilities are NOT used (" in the incompatible line. */
74
+ readonly withheld: string;
75
+ }
76
+ /**
77
+ * Run the runtime interface gate and answer whether the loaded base's shared capabilities may be
78
+ * used.
79
+ *
80
+ * `false` is a DEGRADATION the caller answers by taking its own full-degradation route (no runtime,
81
+ * own prompt defaults, the compatibility gate skipped, legacy provisioning) — never a refused mount,
82
+ * and never a throw. `cannot-tell` warns and answers `true`: "cannot tell" is not a negative answer,
83
+ * so the base is used normally. The verdict's own reason is the base's; only the surrounding sentence
84
+ * is the caller's ({@link DegradeWords}).
85
+ */
86
+ export declare function gateOrDegrade(module: unknown, log: GateLogger | undefined, words: DegradeWords): boolean;
87
+ /**
88
+ * The guard that makes a loader's "never throws" true: every failure is one warning plus
89
+ * `undefined`. The caller owns the sentence's tail (`words`) and its own cache discipline — a
90
+ * FAILED load is not cached, so this must return `undefined` rather than reject (see
91
+ * {@link createLoadCache}).
92
+ */
93
+ export declare function loadGuarded<O, T>(options: O, loadOnce: (options: O) => Promise<T | undefined>, log: GateLogger | undefined, words: DegradeWords): Promise<T | undefined>;
94
+ /** The per-process result of a guarded load: `load` is idempotent, `clear` is for disposal. */
95
+ export interface LoadCache<O, T> {
96
+ load(options: O): Promise<T | undefined>;
97
+ /** Drop the result and the in-flight promise, so the next `load` runs again (unmount/reload). */
98
+ clear(): void;
99
+ }
100
+ /**
101
+ * Wrap a guarded loader in the cache discipline both startups share: the first call runs it, every
102
+ * later and concurrent call shares its result, and a FAILED load is NOT cached. A failure is "cannot
103
+ * tell", not a verdict — and it may be transient (offline at boot, a half-written install) — so a
104
+ * profile reload, or a second instance in the same process, retries instead of replaying the first
105
+ * failure for the whole process lifetime.
106
+ */
107
+ export declare function createLoadCache<O, T>(guarded: (options: O) => Promise<T | undefined>): LoadCache<O, T>;
108
+ /**
109
+ * Whether a Cordis context is still alive.
110
+ *
111
+ * Cordis clears `fiber.uid` on disposal, and every later context call then throws
112
+ * `INACTIVE_EFFECT`. `apply` awaits environment preparation, so a profile reload or unload can land
113
+ * inside that window and half-register a plugin (the Remote face cannot be rolled back). A fiberless
114
+ * stub counts as active: an unknown shape is "cannot tell", never "disposed".
115
+ *
116
+ * Generic Cordis liveness rather than interface-gate knowledge — but the two plugins' startup paths
117
+ * share it byte-for-byte, so it lives with their one shared shim, where the two readings of "is my
118
+ * context alive" cannot drift.
119
+ */
120
+ export declare function stillActive(ctx: unknown): boolean;
51
121
  //# sourceMappingURL=interface_gate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interface_gate.d.ts","sourceRoot":"","sources":["../../src/interface_gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE/D,mGAAmG;AACnG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAA;CAC5C;AAkBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAE/D;AA8BD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,gBAAgB,CAuB3F"}
1
+ {"version":3,"file":"interface_gate.d.ts","sourceRoot":"","sources":["../../src/interface_gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE/D,mGAAmG;AACnG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAA;CAC5C;AAkBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAE/D;AA8BD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,gBAAgB,CAuB3F;AAED,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAeD;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,mGAAmG;IACnG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,wGAAwG;IACxG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAiBxG;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EACpC,OAAO,EAAE,CAAC,EACV,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EAChD,GAAG,EAAE,UAAU,GAAG,SAAS,EAC3B,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAUxB;AAED,+FAA+F;AAC/F,MAAM,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAA;IACxC,iGAAiG;IACjG,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAyBtG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGjD"}
@@ -84,6 +84,12 @@ export declare function runtimeFromCompat(module: CompatModule): CompatRuntime;
84
84
  * Run the gate. Call this FIRST in `apply()`: on a refusal nothing has been registered yet, so
85
85
  * "do not load" costs nothing to unwind. Never throws — not even with a throwing logger, because
86
86
  * "cannot tell" is not "incompatible".
87
+ *
88
+ * The check itself is the BASE's `provision` (gather + verdict + logging, one call). The local
89
+ * fallback stays on purpose (option A): the interface-generation axis cannot gate this dependency
90
+ * (same-generation bases never read as `incompatible`), and raising the peer floor would degrade
91
+ * every host on a base that predates the fix — so a `provision` that cannot be performed still
92
+ * leaves the plugin loading.
87
93
  * @param ctx - the plugin's context.
88
94
  * @param log - the plugin's logger.
89
95
  * @param compat - the loaded gate runtime (see `src/envinit.ts`).
@@ -1 +1 @@
1
- {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../../src/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,UAAU,EACV,aAAa,EACb,eAAe,EAChB,MAAM,yBAAyB,CAAA;AAIhC,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AAE7C,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,cAAc,yBAAyB,CAAC,CAAA;AAEnE,mFAAmF;AACnF,eAAO,MAAM,cAAc,4BAA4B,CAAA;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAMvD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA6C,CAAA;AAE1F,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAmE,CAAA;AAKjH,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,+FAA+F;IAC/F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAED,0BAA0B;AAC1B,UAAU,SAAS;IACjB,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAChC;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,2EAA2E;AAC3E,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAMzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAyBrE;AAoBD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,UAAwB,GAC7B,SAAS,CAqBX;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,aAAa,GACpB,IAAI,CAaN;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAQ1C"}
1
+ {"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../../src/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,YAAY,EACZ,UAAU,EACV,aAAa,EACb,eAAe,EAChB,MAAM,yBAAyB,CAAA;AAIhC,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA;AAE7C,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,cAAc,yBAAyB,CAAC,CAAA;AAEnE,mFAAmF;AACnF,eAAO,MAAM,cAAc,4BAA4B,CAAA;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAMvD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA6C,CAAA;AAE1F,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAmE,CAAA;AAKjH,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,+FAA+F;IAC/F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAED,0BAA0B;AAC1B,UAAU,SAAS;IACjB,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAChC;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,2EAA2E;AAC3E,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAMzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAyBrE;AAoBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,UAAwB,GAC7B,SAAS,CAMX;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,aAAa,GACpB,IAAI,CAaN;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAQ1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avantf/dsh-mem",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "avantf-mem DSH native Cordis plugin: host service + tools + RPC + client tabs 记忆/知识",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -78,10 +78,10 @@
78
78
  },
79
79
  "devDependencies": {
80
80
  "@avantf/dsh-plugin-base": ">=0.3.0 <1.0.0",
81
- "@avantf/mem": "0.6.1",
82
- "@avantf/mem-contract": "0.6.1",
83
- "@avantf/mem-convert": "0.6.1",
84
- "@avantf/mem-provision": "0.6.1",
81
+ "@avantf/mem": "0.6.2",
82
+ "@avantf/mem-contract": "0.6.2",
83
+ "@avantf/mem-convert": "0.6.2",
84
+ "@avantf/mem-provision": "0.6.2",
85
85
  "@types/node": "^22.0.0",
86
86
  "@types/react": "^18.3.0",
87
87
  "lightningcss": "^1.32.0",