@avantf/dsh-mem 0.3.0 → 0.4.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/README.md +89 -28
- package/cordis.patch.yml +17 -0
- package/lib/client.js +155 -14
- package/lib/dsh-build.json +5 -5
- package/lib/engine/mem/config/config_files.d.ts +26 -0
- package/lib/engine/mem/config/domains.d.ts +20 -0
- package/lib/engine/mem/config/loader.d.ts +35 -0
- package/lib/engine/mem/config/paths.d.ts +53 -0
- package/lib/engine/mem/db/chunk.d.ts +26 -0
- package/lib/engine/mem/db/conn.d.ts +13 -0
- package/lib/engine/mem/db/dao/chunks.d.ts +150 -0
- package/lib/engine/mem/db/dao/contradictions.d.ts +98 -0
- package/lib/engine/mem/db/dao/documents.d.ts +51 -0
- package/lib/engine/mem/db/dao/entities.d.ts +67 -0
- package/lib/engine/mem/db/dao/facts.d.ts +472 -0
- package/lib/engine/mem/db/dao/stats.d.ts +14 -0
- package/lib/engine/mem/db/dao/triples.d.ts +80 -0
- package/lib/engine/mem/db/hash.d.ts +2 -0
- package/lib/engine/mem/db/knowledge.d.ts +17 -0
- package/lib/engine/mem/db/migrations.d.ts +71 -0
- package/lib/engine/mem/db/port.d.ts +57 -0
- package/lib/engine/mem/db/schema.d.ts +26 -0
- package/lib/engine/mem/db/sqlite.d.ts +89 -0
- package/lib/engine/mem/db/store.d.ts +35 -0
- package/lib/engine/mem/db/tokenizer.d.ts +61 -0
- package/lib/engine/mem/db/vectors.d.ts +50 -0
- package/lib/engine/mem/dispatch.d.ts +51 -0
- package/lib/engine/mem/entities/extract.d.ts +84 -0
- package/lib/engine/mem/eval/loader.d.ts +19 -0
- package/lib/engine/mem/eval/metrics.d.ts +25 -0
- package/lib/engine/mem/eval/runner.d.ts +10 -0
- package/lib/engine/mem/eval/write_metrics.d.ts +88 -0
- package/lib/engine/mem/git.d.ts +73 -0
- package/lib/engine/mem/hrr/algebra.d.ts +30 -0
- package/lib/engine/mem/hrr/atoms.d.ts +22 -0
- package/lib/engine/mem/hrr/encode.d.ts +32 -0
- package/lib/engine/mem/hrr/index.d.ts +3 -0
- package/lib/engine/mem/index.d.ts +27 -0
- package/lib/engine/mem/lifecycle/contradiction.d.ts +156 -0
- package/lib/engine/mem/lifecycle/index.d.ts +5 -0
- package/lib/engine/mem/lifecycle/maintenance.d.ts +48 -0
- package/lib/engine/mem/lifecycle/presence.d.ts +29 -0
- package/lib/engine/mem/lifecycle/tick.d.ts +43 -0
- package/lib/engine/mem/lifecycle/trust.d.ts +90 -0
- package/lib/engine/mem/modelBootstrap.d.ts +207 -0
- package/lib/engine/mem/router.d.ts +51 -0
- package/lib/engine/mem/runtime.d.ts +232 -0
- package/lib/engine/mem/store/common.d.ts +82 -0
- package/lib/engine/mem/store/doc_files.d.ts +125 -0
- package/lib/engine/mem/store/document_text.d.ts +47 -0
- package/lib/engine/mem/store/floors.d.ts +191 -0
- package/lib/engine/mem/store/hybrid.d.ts +232 -0
- package/lib/engine/mem/store/ingest_guard.d.ts +25 -0
- package/lib/engine/mem/store/knowledge.d.ts +483 -0
- package/lib/engine/mem/store/lexical.d.ts +67 -0
- package/lib/engine/mem/store/memory.d.ts +543 -0
- package/lib/engine/mem/store/source_picker.d.ts +43 -0
- package/lib/engine/mem-contract/config.d.ts +129 -0
- package/lib/engine/mem-contract/env.d.ts +31 -0
- package/lib/engine/mem-contract/family.d.ts +25 -0
- package/lib/engine/mem-contract/index.d.ts +8 -0
- package/lib/engine/mem-contract/log.d.ts +29 -0
- package/lib/engine/mem-contract/remote.d.ts +80 -0
- package/lib/engine/mem-contract/text.d.ts +92 -0
- package/lib/engine/mem-contract/tools.d.ts +284 -0
- package/lib/engine/mem-contract/types.d.ts +615 -0
- package/lib/engine/mem-retrieval/adapters/auto_vstore.d.ts +47 -0
- package/lib/engine/mem-retrieval/adapters/hnswlib.d.ts +174 -0
- package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +121 -0
- package/lib/engine/mem-retrieval/adapters/local_numpy.d.ts +28 -0
- package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +52 -0
- package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +10 -0
- package/lib/engine/mem-retrieval/adapters/warm_gate.d.ts +46 -0
- package/lib/engine/mem-retrieval/budget.d.ts +30 -0
- package/lib/engine/mem-retrieval/fusion.d.ts +53 -0
- package/lib/engine/mem-retrieval/index.d.ts +14 -0
- package/lib/engine/mem-retrieval/interfaces.d.ts +82 -0
- package/lib/engine/mem-retrieval/log.d.ts +11 -0
- package/lib/engine/mem-retrieval/registry.d.ts +23 -0
- package/lib/engine/mem-retrieval/rerank.d.ts +13 -0
- package/lib/engine/mem-retrieval/stats.d.ts +64 -0
- package/lib/engine/mem-retrieval/text_budget.d.ts +52 -0
- package/lib/index.js +1773 -473
- package/lib/interface-version.json +2 -2
- package/lib/types/client/domains.d.ts +5 -4
- package/lib/types/client/domains.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/ingest.d.ts +1 -1
- package/lib/types/client/wire.d.ts +36 -0
- package/lib/types/client/wire.d.ts.map +1 -0
- package/lib/types/envinit-bootstrap.d.ts +3 -2
- package/lib/types/envinit.d.ts +2 -19
- package/lib/types/envinit.d.ts.map +1 -1
- package/lib/types/hints.d.ts +1 -1
- package/lib/types/index.d.ts +8 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/open.d.ts +61 -11
- package/lib/types/open.d.ts.map +1 -1
- package/lib/types/prompt.d.ts +19 -1
- package/lib/types/prompt.d.ts.map +1 -1
- package/lib/types/provision.d.ts +1 -1
- package/lib/types/provision.d.ts.map +1 -1
- package/lib/types/reconcile.d.ts +64 -0
- package/lib/types/reconcile.d.ts.map +1 -0
- package/lib/types/remote.d.ts +53 -0
- package/lib/types/remote.d.ts.map +1 -1
- package/lib/types/render.d.ts +35 -0
- package/lib/types/render.d.ts.map +1 -0
- package/lib/types/tool_schema.d.ts +3 -13
- package/lib/types/tool_schema.d.ts.map +1 -1
- package/package.json +27 -18
- package/lib/client.js.map +0 -1
- package/lib/types/client/domains.js +0 -25
- package/lib/types/client/domains.js.map +0 -1
- package/lib/types/client/index.js +0 -1371
- package/lib/types/client/index.js.map +0 -1
- package/lib/types/client/ingest.js +0 -30
- package/lib/types/client/ingest.js.map +0 -1
- package/lib/types/client/paging.js +0 -36
- package/lib/types/client/paging.js.map +0 -1
- package/lib/types/client/summarize.js +0 -79
- package/lib/types/client/summarize.js.map +0 -1
- package/lib/types/data_home.js +0 -26
- package/lib/types/data_home.js.map +0 -1
- package/lib/types/envinit-bootstrap.js +0 -333
- package/lib/types/envinit.js +0 -402
- package/lib/types/envinit.js.map +0 -1
- package/lib/types/hints.js +0 -53
- package/lib/types/hints.js.map +0 -1
- package/lib/types/index.js +0 -755
- package/lib/types/index.js.map +0 -1
- package/lib/types/inject.js +0 -14
- package/lib/types/inject.js.map +0 -1
- package/lib/types/interface_gate.js +0 -84
- package/lib/types/interface_gate.js.map +0 -1
- package/lib/types/open.js +0 -133
- package/lib/types/open.js.map +0 -1
- package/lib/types/prompt.js +0 -148
- package/lib/types/prompt.js.map +0 -1
- package/lib/types/provision.js +0 -177
- package/lib/types/provision.js.map +0 -1
- package/lib/types/remote.js +0 -247
- package/lib/types/remote.js.map +0 -1
- package/lib/types/tool_schema.js +0 -149
- package/lib/types/tool_schema.js.map +0 -1
package/lib/types/index.js
DELETED
|
@@ -1,755 +0,0 @@
|
|
|
1
|
-
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
-
var useValue = arguments.length > 2;
|
|
3
|
-
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
-
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
-
}
|
|
6
|
-
return useValue ? value : void 0;
|
|
7
|
-
};
|
|
8
|
-
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
-
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
-
var _, done = false;
|
|
14
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
-
var context = {};
|
|
16
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
-
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
-
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
-
if (kind === "accessor") {
|
|
21
|
-
if (result === void 0) continue;
|
|
22
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
-
}
|
|
27
|
-
else if (_ = accept(result)) {
|
|
28
|
-
if (kind === "field") initializers.unshift(_);
|
|
29
|
-
else descriptor[key] = _;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
-
done = true;
|
|
34
|
-
};
|
|
35
|
-
import { existsSync } from 'node:fs';
|
|
36
|
-
import { join } from 'node:path';
|
|
37
|
-
import { buildPromptSections, promptFileSpecs, promptTextWarnings } from './prompt.js';
|
|
38
|
-
import { hintText, messageText } from './hints.js';
|
|
39
|
-
import z from '@deepseek-ai/schemastery';
|
|
40
|
-
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
41
|
-
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol';
|
|
42
|
-
import { buildRuntime, dispatchToolKey, provisionToolchainAsync, supportsToolKey, warmSemanticAsync, warmTokenizerAsync, } from '@avantf/mem';
|
|
43
|
-
import { REMEMBER_TOOL, RECALL_TOOL, ADMIN_TOOL, KB_TOOL, QUERY_TOOL, TOOL_SPECS, createConsoleLogger, describeError, modelFacingToolResult, toolOk, toolErr, validationError, remoteErrorText, } from '@avantf/mem-contract';
|
|
44
|
-
import { hostContribution } from './remote.js';
|
|
45
|
-
import { configDataHome } from './data_home.js';
|
|
46
|
-
import { provision, registerCompatMegaphone, verifyRegisteredFaces } from './provision.js';
|
|
47
|
-
import { managedModelSpec, MODEL_ITEM, PANDOC_ITEM, loadEnvinit, } from './envinit.js';
|
|
48
|
-
import { parseToolsConfig, resolveToolsDir } from '@avantf/mem-provision';
|
|
49
|
-
import { resetPandocResolution, setPandocProvisioning } from '@avantf/mem-convert';
|
|
50
|
-
import { openDocumentPath } from './open.js';
|
|
51
|
-
import { browseDirectory, classifySource } from '@avantf/mem';
|
|
52
|
-
import { flattenToolSpec } from './tool_schema.js';
|
|
53
|
-
export const name = 'avantf-mem';
|
|
54
|
-
export { inject } from './inject.js';
|
|
55
|
-
// The section text lives in its own module so it is testable without this entry's harness imports.
|
|
56
|
-
export { KNOWLEDGE_PROMPT_SECTION, MEMORY_PROMPT_SECTION } from './prompt.js';
|
|
57
|
-
export const Config = z.object({
|
|
58
|
-
mode: z.union([z.const('cordis'), z.const('mcp')]).default('cordis'),
|
|
59
|
-
// Deliberately NO default: this is a CONFIGURED value (layer ②), so `$AVANTF_HOME` (④) has to be
|
|
60
|
-
// able to outrank it — and a literal `~/.avantf` default would make "unset" indistinguishable from
|
|
61
|
-
// "configured as the default", which is how merely having a default used to kill the env var. Left
|
|
62
|
-
// unset, resolution falls through ④ to the default (see `configDataHome`).
|
|
63
|
-
dataHome: z.string(),
|
|
64
|
-
});
|
|
65
|
-
/**
|
|
66
|
-
* Facts whose entity/triple rows the heartbeat may rebuild per beat.
|
|
67
|
-
*
|
|
68
|
-
* Tagging is ~0.02 ms for an ordinary fact (nodejieba, measured), so this batch is tens of
|
|
69
|
-
* milliseconds on a quiet moment — small enough for a 60-minute heartbeat and large enough to
|
|
70
|
-
* adopt a whole corpus over a few beats after an extraction change.
|
|
71
|
-
*/
|
|
72
|
-
const ENTITY_SWEEP_PER_HEARTBEAT = 2000;
|
|
73
|
-
/** Minimum spacing between corpus drift checks (`tools/result` fires for EVERY tool). */
|
|
74
|
-
const RECONCILE_MIN_INTERVAL_MS = 2000;
|
|
75
|
-
// ─── dispatch: validate with the contract schema, never leak a raw exception ──────────────────
|
|
76
|
-
//
|
|
77
|
-
// The key → runtime table is the ENGINE's (`@avantf/mem`'s `dispatchToolKey`), shared with the MCP
|
|
78
|
-
// server. It used to be duplicated here, and the copy that drifted was the MCP one: the four `kb_*`
|
|
79
|
-
// tools were advertised to the model and answered `unknown tool key` on every call.
|
|
80
|
-
async function runToolSpec(spec, rt, args) {
|
|
81
|
-
const parsed = spec.input.safeParse(args ?? {});
|
|
82
|
-
if (!parsed.success) {
|
|
83
|
-
return validationError(spec.name, parsed.error.issues);
|
|
84
|
-
}
|
|
85
|
-
if (!supportsToolKey(spec.key))
|
|
86
|
-
return toolErr(`unknown tool ${spec.key}`);
|
|
87
|
-
try {
|
|
88
|
-
const value = await dispatchToolKey(rt, spec.key, parsed.data);
|
|
89
|
-
// Uniform agent-facing envelope, identical to the MCP surface (`ToolEnvelope`):
|
|
90
|
-
// every success is `{ok:true,result}` regardless of the payload's shape.
|
|
91
|
-
// `mem_admin`'s fact views lose the retention diagnostics here; the Remote gateway that
|
|
92
|
-
// drives the 记忆/知识 tabs calls the same table and keeps them.
|
|
93
|
-
const shaped = modelFacingToolResult(spec.key, parsed.data.action, value);
|
|
94
|
-
return toolOk(shaped);
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
return toolErr(error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const OUTPUT = {
|
|
101
|
-
schema: { type: 'object', additionalProperties: true },
|
|
102
|
-
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
103
|
-
};
|
|
104
|
-
function present(spec, args) {
|
|
105
|
-
return { card: 'generic', title: spec.name, kind: 'other', rawInput: args };
|
|
106
|
-
}
|
|
107
|
-
/** Drop wire keys explicitly set to undefined so they cannot clobber defaults. */
|
|
108
|
-
function defined(args) {
|
|
109
|
-
return Object.fromEntries(Object.entries(args ?? {}).filter(([, value]) => value !== undefined));
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Pass-through gateway mirroring the five agent tools. Every payload is
|
|
113
|
-
* re-validated with the contract zod union before dispatch (same guarantee the
|
|
114
|
-
* tools give the model), and results are wrapped in the contract's
|
|
115
|
-
* `{ok:true,value}` / `{ok:false,error,violations}` RemoteEnvelope the client decodes.
|
|
116
|
-
*/
|
|
117
|
-
let AvantfMemGateway = (() => {
|
|
118
|
-
let _classSuper = TypertRemoteService;
|
|
119
|
-
let _instanceExtraInitializers = [];
|
|
120
|
-
let _remember_decorators;
|
|
121
|
-
let _recall_decorators;
|
|
122
|
-
let _admin_decorators;
|
|
123
|
-
let _kb_decorators;
|
|
124
|
-
let _query_decorators;
|
|
125
|
-
let _classifySource_decorators;
|
|
126
|
-
let _browseDir_decorators;
|
|
127
|
-
let _kbDomains_decorators;
|
|
128
|
-
let _kbAddDomain_decorators;
|
|
129
|
-
let _openDoc_decorators;
|
|
130
|
-
return class AvantfMemGateway extends _classSuper {
|
|
131
|
-
static {
|
|
132
|
-
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
133
|
-
_remember_decorators = [Remote('remember')];
|
|
134
|
-
_recall_decorators = [Remote('recall')];
|
|
135
|
-
_admin_decorators = [Remote('admin')];
|
|
136
|
-
_kb_decorators = [Remote('kb')];
|
|
137
|
-
_query_decorators = [Remote('query')];
|
|
138
|
-
_classifySource_decorators = [Remote('classifySource')];
|
|
139
|
-
_browseDir_decorators = [Remote('browseDir')];
|
|
140
|
-
_kbDomains_decorators = [Remote('kbDomains')];
|
|
141
|
-
_kbAddDomain_decorators = [Remote('kbAddDomain')];
|
|
142
|
-
_openDoc_decorators = [Remote('openDoc')];
|
|
143
|
-
__esDecorate(this, null, _remember_decorators, { kind: "method", name: "remember", static: false, private: false, access: { has: obj => "remember" in obj, get: obj => obj.remember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
144
|
-
__esDecorate(this, null, _recall_decorators, { kind: "method", name: "recall", static: false, private: false, access: { has: obj => "recall" in obj, get: obj => obj.recall }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
145
|
-
__esDecorate(this, null, _admin_decorators, { kind: "method", name: "admin", static: false, private: false, access: { has: obj => "admin" in obj, get: obj => obj.admin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
146
|
-
__esDecorate(this, null, _kb_decorators, { kind: "method", name: "kb", static: false, private: false, access: { has: obj => "kb" in obj, get: obj => obj.kb }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
147
|
-
__esDecorate(this, null, _query_decorators, { kind: "method", name: "query", static: false, private: false, access: { has: obj => "query" in obj, get: obj => obj.query }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
|
-
__esDecorate(this, null, _classifySource_decorators, { kind: "method", name: "classifySource", static: false, private: false, access: { has: obj => "classifySource" in obj, get: obj => obj.classifySource }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
149
|
-
__esDecorate(this, null, _browseDir_decorators, { kind: "method", name: "browseDir", static: false, private: false, access: { has: obj => "browseDir" in obj, get: obj => obj.browseDir }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
150
|
-
__esDecorate(this, null, _kbDomains_decorators, { kind: "method", name: "kbDomains", static: false, private: false, access: { has: obj => "kbDomains" in obj, get: obj => obj.kbDomains }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
151
|
-
__esDecorate(this, null, _kbAddDomain_decorators, { kind: "method", name: "kbAddDomain", static: false, private: false, access: { has: obj => "kbAddDomain" in obj, get: obj => obj.kbAddDomain }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
152
|
-
__esDecorate(this, null, _openDoc_decorators, { kind: "method", name: "openDoc", static: false, private: false, access: { has: obj => "openDoc" in obj, get: obj => obj.openDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
153
|
-
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
154
|
-
}
|
|
155
|
-
rt = __runInitializers(this, _instanceExtraInitializers);
|
|
156
|
-
constructor(ctx, rt) {
|
|
157
|
-
super(ctx, 'avantfMem');
|
|
158
|
-
this.rt = rt;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Run one already-validated tool call and wrap the answer in the contract envelope.
|
|
162
|
-
*
|
|
163
|
-
* `T` is the payload the tool key is DECLARED to answer, spelled at each `@Remote` below; this used
|
|
164
|
-
* to be `unknown` everywhere, which made the host half the one seam in the type chain where nothing
|
|
165
|
-
* said what crosses the wire. The single `as T` is unavoidable and is the honest part: the dispatch
|
|
166
|
-
* table is keyed by `ToolSpec.key` (a `string`), so its result is `unknown` by construction. Naming
|
|
167
|
-
* the type is what turns "the client assumed a shape" into "the engine's own type is what the host
|
|
168
|
-
* claims to send".
|
|
169
|
-
*/
|
|
170
|
-
async call(key, spec, payload) {
|
|
171
|
-
const parsed = spec.input.safeParse(payload);
|
|
172
|
-
if (!parsed.success) {
|
|
173
|
-
// Same message + violations format the agent tools return (contract helper).
|
|
174
|
-
return validationError(spec.name, parsed.error.issues);
|
|
175
|
-
}
|
|
176
|
-
try {
|
|
177
|
-
const value = await dispatchToolKey(this.rt, key, parsed.data);
|
|
178
|
-
return { ok: true, value: (value ?? null) };
|
|
179
|
-
}
|
|
180
|
-
catch (error) {
|
|
181
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Each payload is written as the ENGINE type that produces it (`StoreResult<…>`, or a contract
|
|
186
|
-
* interface), never as a hand-copied shape: if a store method's result changes, the declaration
|
|
187
|
-
* here stops compiling instead of quietly misdescribing the wire.
|
|
188
|
-
*/
|
|
189
|
-
remember(args) {
|
|
190
|
-
const payload = defined(args);
|
|
191
|
-
return this.call('remember', REMEMBER_TOOL, { action: 'add', ...payload });
|
|
192
|
-
}
|
|
193
|
-
recall(args) {
|
|
194
|
-
const payload = defined(args);
|
|
195
|
-
return this.call('recall', RECALL_TOOL, { action: 'search', ...payload });
|
|
196
|
-
}
|
|
197
|
-
admin(args) {
|
|
198
|
-
const payload = defined(args);
|
|
199
|
-
return this.call('admin', ADMIN_TOOL, { action: 'list', ...payload });
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* The one method that keeps `unknown`, and deliberately: its payload is whichever of the seven
|
|
203
|
-
* corpus actions the caller asked for (a page, one document, an ingest report, a conflict report, a
|
|
204
|
-
* reindex plan …), so there is no single engine type to name. The engine's `KbDispatch` spells all
|
|
205
|
-
* seven out; the client names its expectation at each call site (`callRemote<…>(…)`).
|
|
206
|
-
*/
|
|
207
|
-
kb(args) {
|
|
208
|
-
// No safe default action for kb — a missing action fails contract validation
|
|
209
|
-
// with an explicit violation instead of guessing.
|
|
210
|
-
return this.call('kb', KB_TOOL, defined(args));
|
|
211
|
-
}
|
|
212
|
-
query(args) {
|
|
213
|
-
return this.call('query', QUERY_TOOL, defined(args));
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* UI-only: what IS this string? Answered against the ingestion boundary, so the UI can label the
|
|
217
|
-
* input and pick the right `kb_manage` action (URL → fetch, file → ingest, directory → import,
|
|
218
|
-
* text → ingest the string itself, missing → refuse and say why).
|
|
219
|
-
*/
|
|
220
|
-
async classifySource(args) {
|
|
221
|
-
try {
|
|
222
|
-
const payload = defined(args);
|
|
223
|
-
const text = typeof payload.text === 'string' ? payload.text : '';
|
|
224
|
-
return { ok: true, value: classifySource(text, this.rt.config.knowledge.ingest) };
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
/** UI-only: one directory listing for the 选择 picker (same boundary as ingestion). */
|
|
231
|
-
async browseDir(args) {
|
|
232
|
-
try {
|
|
233
|
-
const payload = defined(args);
|
|
234
|
-
const path = typeof payload.path === 'string' && payload.path !== '' ? payload.path : undefined;
|
|
235
|
-
return { ok: true, value: browseDirectory(path, this.rt.config.knowledge.ingest) };
|
|
236
|
-
}
|
|
237
|
-
catch (error) {
|
|
238
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* UI-only: the 知识页 domain picker's options — the configured `knowledge.domains` allowlist
|
|
243
|
-
* together with the domains the library already holds — plus whether the allowlist is active
|
|
244
|
-
* (non-empty). The host is the only side that knows either half.
|
|
245
|
-
*/
|
|
246
|
-
async kbDomains(_args) {
|
|
247
|
-
try {
|
|
248
|
-
return { ok: true, value: this.rt.knowledge.domainCatalog() };
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* UI-only: add one 知识域 to the store config's `domains` allowlist AND to the live set, so the
|
|
256
|
-
* 知识 tab's 「+」 works without a restart. No agent tool mirrors this on purpose — the model must
|
|
257
|
-
* stay bounded by the configured list, while the user can widen it.
|
|
258
|
-
*/
|
|
259
|
-
async kbAddDomain(args) {
|
|
260
|
-
try {
|
|
261
|
-
const payload = defined(args);
|
|
262
|
-
const domain = typeof payload.domain === 'string' ? payload.domain : '';
|
|
263
|
-
return { ok: true, value: this.rt.knowledge.addDomain(domain) };
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* UI-only: hand one managed document file — or the directory that holds it — to the user's
|
|
271
|
-
* editor. No agent tool mirrors this, so it carries its own envelope instead of going through
|
|
272
|
-
* `dispatchToolKey`; the path is resolved from `doc_id` here, so nothing the client sends is
|
|
273
|
-
* trusted.
|
|
274
|
-
*/
|
|
275
|
-
async openDoc(args) {
|
|
276
|
-
try {
|
|
277
|
-
const payload = defined(args);
|
|
278
|
-
const docId = typeof payload.doc_id === 'number' ? payload.doc_id : undefined;
|
|
279
|
-
if (docId === undefined)
|
|
280
|
-
return { ok: false, error: 'openDoc 需要 doc_id' };
|
|
281
|
-
const path = this.rt.knowledge.docFilePath(docId);
|
|
282
|
-
if (path === null)
|
|
283
|
-
return { ok: false, error: `文档 #${String(docId)} 不存在` };
|
|
284
|
-
const target = payload.target === 'dir' ? 'dir' : 'file';
|
|
285
|
-
if (target === 'file' && !existsSync(path)) {
|
|
286
|
-
return { ok: false, error: `受管文件不存在:${path}(重新摄入会重建它)` };
|
|
287
|
-
}
|
|
288
|
-
const outcome = openDocumentPath(path, target, this.rt.config.knowledge.open.editor);
|
|
289
|
-
return { ok: true, value: outcome };
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
return { ok: false, error: remoteErrorText(error) };
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
})();
|
|
297
|
-
export { AvantfMemGateway };
|
|
298
|
-
/**
|
|
299
|
-
* Startup/lifecycle logging sink.
|
|
300
|
-
*
|
|
301
|
-
* DSH's `ctx.logger` currently has only a buffered exporter, so lines written
|
|
302
|
-
* through it never reach the terminal. Startup diagnostics therefore go to
|
|
303
|
-
* stderr (the CLI/MCP convention, keeping stdout free for data) and are mirrored
|
|
304
|
-
* into the host logger for any in-app log surface.
|
|
305
|
-
*/
|
|
306
|
-
function hostLogger(ctx) {
|
|
307
|
-
const local = createConsoleLogger('[avantf-mem]');
|
|
308
|
-
const host = ctx.logger;
|
|
309
|
-
return {
|
|
310
|
-
info: (message) => { local.info(message); host.info(message); },
|
|
311
|
-
warn: (message) => { local.warn(message); host.warn(message); },
|
|
312
|
-
error: (message) => { local.error(message); host.error(message); },
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Whether this plugin's fiber is still alive after the environment await.
|
|
317
|
-
*
|
|
318
|
-
* The environment gate can take the whole startup budget (15 s on a first run) and the framework may
|
|
319
|
-
* even fetch the gate's base during it. A profile reload or unload landing inside that window clears
|
|
320
|
-
* the fiber's uid, after which every Cordis call throws `INACTIVE_EFFECT` — the plugin would then
|
|
321
|
-
* half-register (the Remote face cannot be rolled back) instead of stopping cleanly. An unknown
|
|
322
|
-
* shape is "cannot tell", so only an explicitly cleared uid counts as disposed.
|
|
323
|
-
*/
|
|
324
|
-
function stillActive(ctx) {
|
|
325
|
-
const fiber = ctx.fiber;
|
|
326
|
-
return fiber === undefined || fiber.uid !== null;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Declare the expensive resources through the family framework, warm the tokenizer and the embedding
|
|
330
|
-
* model, and continue initialisation as each item settles.
|
|
331
|
-
*
|
|
332
|
-
* Everything here is fire-and-forget: `background` items never occupy the startup budget, and the
|
|
333
|
-
* callbacks ARE the "continue once the resource is there" step (framework DESIGN §7.2 step 5).
|
|
334
|
-
*
|
|
335
|
-
* **What the framework owns here.** `mem:pandoc` and `mem:model` are framework items. The embedding
|
|
336
|
-
* model is a `model-cache` in the framework's `flat` layout, so its files land at
|
|
337
|
-
* `<home>/models/<repo>/<file>` — the tree `semantic.cache_dir` resolves to (the family root, via
|
|
338
|
-
* `managedRoots`) and the shape `@huggingface/transformers` reads under `env.cacheDir`. The runtime
|
|
339
|
-
* therefore loads the framework's copy instead of fetching a second one.
|
|
340
|
-
*
|
|
341
|
-
* The model is NOT declared when the runtime's cache dir is somewhere else (`AVANTF_MEM_MODEL_CACHE`
|
|
342
|
-
* — `managedModelSpec` decides): the framework's root would then collect a copy nothing reads. In
|
|
343
|
-
* that case, and whenever no item will warm the model, the warm is started here directly.
|
|
344
|
-
*
|
|
345
|
-
* Outcomes handled below:
|
|
346
|
-
*
|
|
347
|
-
* - `mem:model` settles (present/installed) → warm the semantic path from the managed root, now
|
|
348
|
-
* that the files are on disk. That ordering is the whole point: the runtime's constructor warm is
|
|
349
|
-
* deferred while the framework is up (`deferWarm`), so nothing can race the install, and the
|
|
350
|
-
* explicit warm here is what starts the model.
|
|
351
|
-
* - `mem:model` failed/skipped → one WARNING, then STILL warm: with `semantic.auto_download` on the
|
|
352
|
-
* runtime takes its own download path, and with it off the semantic leg degrades to FTS+entity.
|
|
353
|
-
* Either way the mount is untouched.
|
|
354
|
-
* - `mem:pandoc` unavailable → point the converter back at the pre-migration tools directory, so an
|
|
355
|
-
* already-installed copy (or one on PATH) keeps document ingestion working.
|
|
356
|
-
* - anything failed/skipped → one WARNING, with the framework's stable reason code.
|
|
357
|
-
*
|
|
358
|
-
* @param env - the loaded framework runtime.
|
|
359
|
-
* @param rt - the built engine runtime (its resolved config drives the specs).
|
|
360
|
-
* @param logger - the plugin's logger.
|
|
361
|
-
* @param waitFor - the host's readiness signal, for the tokenizer warm.
|
|
362
|
-
*/
|
|
363
|
-
function startManagedResources(env, rt, logger, waitFor) {
|
|
364
|
-
const { semantic, tools } = rt.config.common;
|
|
365
|
-
// `undefined` in two cases: the semantic backend is not a local model, or the runtime reads models
|
|
366
|
-
// from an operator override instead of the framework root (see `managedModelSpec`).
|
|
367
|
-
const model = managedModelSpec(semantic, env.roots.models);
|
|
368
|
-
const plan = {
|
|
369
|
-
pandoc: tools.auto_install,
|
|
370
|
-
archiveMirrors: tools.mirror,
|
|
371
|
-
// The item is still declared when downloads are off — its `skipped (policy/download-disabled)`
|
|
372
|
-
// line is the diagnosis — but the framework then probes disk only, never the network.
|
|
373
|
-
modelAutoDownload: semantic.auto_download,
|
|
374
|
-
model,
|
|
375
|
-
};
|
|
376
|
-
// The tokenizer needs no provisioning — nodejieba is an optional npm dependency — so it warms
|
|
377
|
-
// behind the same host-readiness signal the legacy sweep used.
|
|
378
|
-
warmTokenizerAsync(rt, { ...(waitFor === undefined ? {} : { waitFor }) });
|
|
379
|
-
if (model === undefined && semantic.backend === 'local_bge') {
|
|
380
|
-
// No `mem:model` item owns the model: nobody will call back, so warm from wherever the runtime
|
|
381
|
-
// reads (the `AVANTF_MEM_MODEL_CACHE` directory). `semantic.auto_download` decides whether a
|
|
382
|
-
// missing cache is fetched or degrades.
|
|
383
|
-
warmSemanticAsync(rt);
|
|
384
|
-
}
|
|
385
|
-
env.provisionResources(plan, (entry) => {
|
|
386
|
-
if (entry.id === MODEL_ITEM) {
|
|
387
|
-
// The model files are (or are not) on disk now; only now may the runtime look. On success it
|
|
388
|
-
// reads `<repo>/<file>` from the managed root; on failure it takes its own download or degrades
|
|
389
|
-
// to FTS+entity. Warming on both paths is deliberate — the warning below is the diagnosis.
|
|
390
|
-
warmSemanticAsync(rt);
|
|
391
|
-
}
|
|
392
|
-
const unavailable = entry.action === 'failed' || entry.action === 'skipped';
|
|
393
|
-
if (unavailable) {
|
|
394
|
-
logger.warn(`envinit: ${entry.id} is ${entry.action} (${String(entry.code)}${entry.reason === undefined ? '' : ` — ${entry.reason}`})`);
|
|
395
|
-
if (entry.id === PANDOC_ITEM) {
|
|
396
|
-
// No legacy directory to fall back TO any more: the resolved managed root is the family
|
|
397
|
-
// root, so this is simply "the converter may install/read it itself".
|
|
398
|
-
const fallback = resolveToolsDir(parseToolsConfig(rt.config.common.tools));
|
|
399
|
-
logger.warn(`envinit: pandoc unavailable from the framework; the converter will use ${fallback} (override with tools.dir or AVANTF_PANDOC)`);
|
|
400
|
-
setPandocProvisioning({
|
|
401
|
-
toolsDir: fallback,
|
|
402
|
-
mirror: rt.config.common.tools.mirror,
|
|
403
|
-
autoInstall: rt.config.common.tools.auto_install,
|
|
404
|
-
logger,
|
|
405
|
-
});
|
|
406
|
-
resetPandocResolution();
|
|
407
|
-
}
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
logger.info(`envinit: ${entry.id} ${entry.action} (${entry.source}${entry.version === undefined ? '' : ` ${entry.version}`})`);
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
export async function apply(ctx, config) {
|
|
414
|
-
const logger = hostLogger(ctx);
|
|
415
|
-
// ── environment initialisation — FIRST, before the runtime allocates anything ────────────────
|
|
416
|
-
// The family base's fixed sequence (its DESIGN §7.2): bootstrap (inlined) → load
|
|
417
|
-
// `@avantf/dsh-plugin-base` → derive the compatibility gate from the loaded module → declare the
|
|
418
|
-
// remaining resources. The check runs before anything is registered, so "do not load" costs
|
|
419
|
-
// nothing to unwind.
|
|
420
|
-
//
|
|
421
|
-
// The base (`@avantf/dsh-plugin-base`, whose `compat` module used to be `@avantf/dsh-compat`) IS
|
|
422
|
-
// the framework the bootstrap loads, and it brings the gate with it: no `mem:compat` item and no
|
|
423
|
-
// managed copy to download. The plugin no longer carries its own self-heal through
|
|
424
|
-
// `@avantf/mem-provision`, whose private root the base deliberately does not read. When the base
|
|
425
|
-
// itself is unavailable the plugin keeps mounting (see below) and falls back to its legacy
|
|
426
|
-
// provisioning path, whose default directories are the pre-migration ones.
|
|
427
|
-
//
|
|
428
|
-
// When the base cannot be obtained at all we do NOT refuse: a `compat:` WARNING is logged and the
|
|
429
|
-
// plugin mounts normally (tools/service/prompt/remote all register), because the gate is a safety
|
|
430
|
-
// net and "cannot tell" is not "incompatible". Only a PROVEN break (`probe-failed`) refuses.
|
|
431
|
-
const env = await loadEnvinit({ log: logger });
|
|
432
|
-
// The await above can last the whole startup budget (15 s on a first run). A profile reload or
|
|
433
|
-
// unload during it clears this fiber's uid, and every later Cordis call — `ctx.effect`, `ctx.on`,
|
|
434
|
-
// `ctx.tools.register` — then throws `INACTIVE_EFFECT`. Re-assert liveness and stop cleanly here
|
|
435
|
-
// rather than half-registering (the Remote face cannot be rolled back).
|
|
436
|
-
if (!stillActive(ctx)) {
|
|
437
|
-
logger.warn('unmounted while preparing the environment; stopping before registering anything');
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
const compat = env?.compat;
|
|
441
|
-
let verdict;
|
|
442
|
-
if (compat === undefined) {
|
|
443
|
-
logger.warn('plugin mount: the dsh compatibility gate is ABSENT (@avantf/dsh-plugin-base unavailable — see the WARNING above); mounting the full plugin anyway');
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
const run = provision(ctx, logger, compat);
|
|
447
|
-
if (!run.verdict.load) {
|
|
448
|
-
registerCompatMegaphone(ctx, run.verdict, logger, compat);
|
|
449
|
-
logger.warn('plugin not loaded: the host dsh API is incompatible (compat check above) — nothing was registered; rebuild with `pnpm build:dsh`');
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
verdict = run.verdict;
|
|
453
|
-
}
|
|
454
|
-
// The model-facing set comes from the contract's `TOOL_SPECS` — ONE list, so adding or splitting a
|
|
455
|
-
// tool cannot leave this file behind. (It WAS hardcoded here, and that is exactly what happened:
|
|
456
|
-
// splitting `kb_manage` into `kb_add`/`kb_list`/`kb_remove`/`kb_reindex` left the plugin still
|
|
457
|
-
// registering the old five until this line was found.)
|
|
458
|
-
const toolSpecs = TOOL_SPECS;
|
|
459
|
-
if (config.mode === 'mcp') {
|
|
460
|
-
// The MCP entry is a standalone stdio process (@avantf/mem-mcp); an in-process
|
|
461
|
-
// DSH plugin cannot own the host's stdio. Warn and mount the cordis toolset.
|
|
462
|
-
logger.warn('config mode=mcp is not served in-process — mounting the cordis toolset; run @avantf/mem-mcp standalone for MCP');
|
|
463
|
-
}
|
|
464
|
-
// Resolved through the CONFIG layer, not used as an explicit value: `$AVANTF_HOME` outranks the
|
|
465
|
-
// profile's `dataHome`, the same way it outranks the work plugin's (see `data_home.ts`).
|
|
466
|
-
const dataHome = configDataHome(config.dataHome);
|
|
467
|
-
// Both halves are logged: what was configured, and where it actually landed — the runtime's own
|
|
468
|
-
// init line repeats the latter, but a reader of this line should not have to join two log lines to
|
|
469
|
-
// find out that a configured value was overridden by the environment.
|
|
470
|
-
logger.info(`plugin mount: mode=${config.mode ?? 'cordis'} dataHome=${dataHome}`
|
|
471
|
-
+ ` (configured ${config.dataHome ?? 'unset'})`);
|
|
472
|
-
let rt;
|
|
473
|
-
try {
|
|
474
|
-
rt = buildRuntime({
|
|
475
|
-
dataHome,
|
|
476
|
-
logger,
|
|
477
|
-
// The family framework owns pandoc and the embedding model when it is up: its managed roots
|
|
478
|
-
// become this process's built-in defaults, so the converter and the semantic backend look
|
|
479
|
-
// where the framework publishes. A `tools.dir` / `semantic.cache_dir` in config.yaml (or an
|
|
480
|
-
// `AVANTF_*` escape hatch) still wins — the framework roots are only layer ①.
|
|
481
|
-
...(env === undefined ? {} : { managedRoots: { tools: env.roots.tools, models: env.roots.models } }),
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
catch (error) {
|
|
485
|
-
// A memory plugin must not be able to stop the host from booting. Everything that can fail here
|
|
486
|
-
// is environmental — a corrupt or locked database, an unwritable data home, or a database
|
|
487
|
-
// written by a NEWER version (`SchemaDowngradeError`) — and the loader treats a throwing `apply`
|
|
488
|
-
// as a failed row, which is enough to fail the whole `dsh web` boot (observed). Degrade instead:
|
|
489
|
-
// the five tools stay registered and each answers with the REASON, so the model and the operator
|
|
490
|
-
// get a diagnosis instead of a missing tool and a silent absence.
|
|
491
|
-
const reason = describeError(error);
|
|
492
|
-
logger.error(`runtime unavailable — mounting DEGRADED (tools answer with the reason): ${reason}`);
|
|
493
|
-
const message = `memory unavailable: ${reason}`;
|
|
494
|
-
for (const spec of toolSpecs) {
|
|
495
|
-
ctx.tools.register(defineTool({
|
|
496
|
-
name: spec.name,
|
|
497
|
-
description: spec.description,
|
|
498
|
-
parameters: flattenToolSpec(spec),
|
|
499
|
-
output: OUTPUT,
|
|
500
|
-
execute: () => Promise.resolve(toolErr(message)),
|
|
501
|
-
presentCall: (args) => present(spec, args),
|
|
502
|
-
}));
|
|
503
|
-
logger.info(`tool registered (DEGRADED): ${spec.name}`);
|
|
504
|
-
}
|
|
505
|
-
logger.warn('client tabs unavailable: no runtime to serve the `avantfMem` remote (fix the error above, then restart)');
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
ctx.provide('avantfMemory', rt);
|
|
509
|
-
logger.info('service registered: avantfMemory');
|
|
510
|
-
// ONE initialization path for everything this plugin must PROVIDE: the pandoc binary every
|
|
511
|
-
// document conversion goes through, and the embedding model + tokenizer warm-up.
|
|
512
|
-
//
|
|
513
|
-
// WHICH mechanism does the fetching is decided by whether the family framework came up:
|
|
514
|
-
//
|
|
515
|
-
// - framework available → `mem:pandoc` and `mem:model` are declared as BACKGROUND items and
|
|
516
|
-
// dispatched now; the plugin keeps initialising and continues on each `onSettled`. Nothing here
|
|
517
|
-
// waits for a 35 MB archive or a ~90 MB model. The engine's managed roots were already pointed
|
|
518
|
-
// at the family root above, so the semantic backend reads the very tree the framework fills,
|
|
519
|
-
// and the warm starts from the model item's settle callback rather than racing it.
|
|
520
|
-
// - framework unavailable → the legacy `@avantf/mem-provision` sweep, over the pre-migration
|
|
521
|
-
// default directories, exactly as before.
|
|
522
|
-
//
|
|
523
|
-
// nodejieba's dictionary parse is ~1.2 s of SYNCHRONOUS main-thread work, so it must not run
|
|
524
|
-
// while the host is still booting: measured, `dsh web` printed its URL line 0.09 s after our
|
|
525
|
-
// "tokenizer ready" line, i.e. the parse was the last thing gating startup. The host's own
|
|
526
|
-
// readiness signal is its loader tree settling (`loader.await()` — what the web app awaits before
|
|
527
|
-
// printing the URL); both paths warm the tokenizer behind it.
|
|
528
|
-
const loader = ctx.get('loader');
|
|
529
|
-
const waitFor = loader?.await === undefined ? undefined : loader.await();
|
|
530
|
-
if (env !== undefined) {
|
|
531
|
-
startManagedResources(env, rt, logger, waitFor);
|
|
532
|
-
}
|
|
533
|
-
else {
|
|
534
|
-
// The verdict the front gate JUST computed is handed to the sweep, whose first step reads `load`
|
|
535
|
-
// (see `provisionToolchainAsync`): an incompatible host provisions NOTHING. Dispatched, not
|
|
536
|
-
// awaited. Omitted when the gate could not run at all (`compat === undefined`), in which case
|
|
537
|
-
// "cannot tell" means the sweep runs as usual.
|
|
538
|
-
void provisionToolchainAsync(rt, {
|
|
539
|
-
...(waitFor === undefined ? {} : { waitFor }),
|
|
540
|
-
...(verdict === undefined ? {} : { dshCompat: verdict }),
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
// Derived state follows the RULES, not the write: a fact written before an extraction change
|
|
544
|
-
// keeps the old entity/triple rows (and the HRR bundle derived from them) until a sweep visits
|
|
545
|
-
// it. ONE bounded batch per call — the store's in-flight guard drops an overlapping call rather
|
|
546
|
-
// than letting two passes select the same rows — and the remainder is picked up next time.
|
|
547
|
-
const sweepEntities = () => {
|
|
548
|
-
void rt.memory.reindexEntities(ENTITY_SWEEP_PER_HEARTBEAT)
|
|
549
|
-
.then((swept) => {
|
|
550
|
-
if (swept.rebuilt > 0) {
|
|
551
|
-
logger.info(`entity sweep: rebuilt ${swept.rebuilt} fact(s) written by older rules (${swept.deferred} still stale)`);
|
|
552
|
-
}
|
|
553
|
-
})
|
|
554
|
-
.catch((error) => {
|
|
555
|
-
logger.warn(`entity sweep failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
556
|
-
});
|
|
557
|
-
};
|
|
558
|
-
// FIRST trigger is here, at mount — not on the first beat. A rule change used to reach old facts
|
|
559
|
-
// only after `heartbeat_minutes` (60 by default), and never at all when that was 0.
|
|
560
|
-
sweepEntities();
|
|
561
|
-
// Trust heartbeat: the active-day clock advances on presence, and the tick performs
|
|
562
|
-
// the settle/forget/idle/purge sweeps (TRUST_MODEL.md §5). `0` = startup pass only.
|
|
563
|
-
const heartbeatMinutes = rt.config.common.trust.presence.heartbeat_minutes;
|
|
564
|
-
if (heartbeatMinutes > 0) {
|
|
565
|
-
ctx.effect(() => {
|
|
566
|
-
const timer = setInterval(() => {
|
|
567
|
-
try {
|
|
568
|
-
rt.memory.trustTick();
|
|
569
|
-
}
|
|
570
|
-
catch (error) {
|
|
571
|
-
logger.warn(`trust tick failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
572
|
-
}
|
|
573
|
-
sweepEntities();
|
|
574
|
-
}, heartbeatMinutes * 60_000);
|
|
575
|
-
const unref = timer.unref;
|
|
576
|
-
if (typeof unref === 'function')
|
|
577
|
-
unref.call(timer);
|
|
578
|
-
logger.info(`trust heartbeat: every ${heartbeatMinutes}min (active-day clock + lifecycle sweep)`);
|
|
579
|
-
return () => clearInterval(timer);
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
// Three usage sections, and only usage: when a fact is worth writing / when to read memory before
|
|
583
|
-
// answering, when the document library is worth querying, and how to CHANGE a document. Retention mechanics stay out of
|
|
584
|
-
// both (the store's business, and the only action such text invites is rewriting a fact to
|
|
585
|
-
// refresh it — which defeats the policy it describes); the module-level reasons are on
|
|
586
|
-
// MEMORY_PROMPT_SECTION and KNOWLEDGE_PROMPT_SECTION.
|
|
587
|
-
//
|
|
588
|
-
// The TEXT is the user's to edit: one `.md` per section under `<data home>/prompts`, ensured and
|
|
589
|
-
// read by the engine's generic `PromptFiles` on this path only (a degraded mount registers no
|
|
590
|
-
// sections at all — see the `catch` above). Read ONCE, here: an edit takes effect on the next
|
|
591
|
-
// start, which keeps "what is in the prompt" the same for the whole life of the process.
|
|
592
|
-
//
|
|
593
|
-
// The directory is SHARED by the family's plugins and each plugin owns its prefix (`mem-*` here,
|
|
594
|
-
// `work-*` in the work engine): every avantf plugin's system prompt sits in one place, and no
|
|
595
|
-
// plugin has to guess which files are its own.
|
|
596
|
-
const promptDir = join(rt.config.home, 'prompts');
|
|
597
|
-
// The loader is the BASE's, taken at RUNTIME off the module the bootstrap already loaded: fixing
|
|
598
|
-
// or extending the shared prompt-file logic takes one base release, not a plugin rebuild.
|
|
599
|
-
// DEGRADATION, when the base (or its kit) is unavailable: the section texts fall back to this
|
|
600
|
-
// plugin's OWN built-in defaults — the defaults are this plugin's content, not shared code — and
|
|
601
|
-
// nothing is written to disk.
|
|
602
|
-
const kit = env?.kit;
|
|
603
|
-
const loadedPrompts = kit?.PromptFiles === undefined
|
|
604
|
-
? promptFileSpecs().map((spec) => ({
|
|
605
|
-
file: spec.file,
|
|
606
|
-
path: join(promptDir, spec.file),
|
|
607
|
-
text: spec.fallback,
|
|
608
|
-
source: 'default',
|
|
609
|
-
wrote: false,
|
|
610
|
-
}))
|
|
611
|
-
: new kit.PromptFiles({ dir: promptDir, logger }).load(promptFileSpecs());
|
|
612
|
-
const promptSections = buildPromptSections(loadedPrompts);
|
|
613
|
-
for (const section of promptSections) {
|
|
614
|
-
ctx.systemPrompt.section(section);
|
|
615
|
-
logger.info(`prompt section registered: ${section.name} (order ${String(section.order)})`);
|
|
616
|
-
}
|
|
617
|
-
// The defaults are held to hard rules by `prompt_section.spec.ts`; edited text is the user's, so
|
|
618
|
-
// it only gets these warnings — injected exactly as written either way.
|
|
619
|
-
for (const warning of promptTextWarnings(promptSections))
|
|
620
|
-
logger.warn(`prompt text: ${warning}`);
|
|
621
|
-
logger.info(`prompt files: ${promptDir} (${loadedPrompts.map((entry) => `${entry.file}:${entry.source}`).join(', ')})`);
|
|
622
|
-
// The CONDITIONAL hint, ONE contribution (DESIGN §12). Recomputed synchronously the moment the
|
|
623
|
-
// agent is handed a USER message, so it is in place before THIS step's prompt is assembled: the
|
|
624
|
-
// provider below cannot await, which is exactly why the probe is the lexical one (`@avantf/mem`'s
|
|
625
|
-
// `relevance` — the semantic leg needs an encode and would land a step late).
|
|
626
|
-
const hints = new WeakMap();
|
|
627
|
-
ctx.on('agent/inbox/inserted', (payload) => {
|
|
628
|
-
const agent = payload.agent;
|
|
629
|
-
if (agent === undefined)
|
|
630
|
-
return;
|
|
631
|
-
// Only what the USER sent moves the verdict. The inbox also carries this plugin's own wake-ups
|
|
632
|
-
// and other plugins' notices; probing those would let a "工作 n1 已结束" wake rewrite a hint
|
|
633
|
-
// that claims to be about the user's last message.
|
|
634
|
-
if (payload.message?.source?.kind !== 'user')
|
|
635
|
-
return;
|
|
636
|
-
const text = messageText(payload.message);
|
|
637
|
-
// Never let a probe failure reach the event bus: a missing hint is harmless (the usage
|
|
638
|
-
// sections still tell the model to query), a throwing listener is only log noise.
|
|
639
|
-
try {
|
|
640
|
-
hints.set(agent, text !== '' && rt.relevance(text));
|
|
641
|
-
}
|
|
642
|
-
catch {
|
|
643
|
-
hints.set(agent, false);
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
ctx.systemPrompt.context({
|
|
647
|
-
name: 'avantf:mem-hint',
|
|
648
|
-
order: 130,
|
|
649
|
-
// The assembly scope IS the agent object (`dsh-agent`'s `assembleContextFor` passes
|
|
650
|
-
// `scope: agent`), so keying by it keeps two sessions from seeing each other's hint. No state
|
|
651
|
-
// for this agent (or a false verdict) renders as empty text, which contributes nothing.
|
|
652
|
-
text: (context) => {
|
|
653
|
-
const hit = context.scope === undefined ? undefined : hints.get(context.scope);
|
|
654
|
-
return hit === undefined ? '' : hintText(hit);
|
|
655
|
-
},
|
|
656
|
-
});
|
|
657
|
-
logger.info('prompt context registered: avantf:mem-hint (order 130)');
|
|
658
|
-
// Corpus reconciliation, done PRECISELY rather than on a timer or a watcher: the agent reaches
|
|
659
|
-
// the corpus through tool calls, so a finished tool call is the moment to ask "did anything under
|
|
660
|
-
// `knowledge.docs.dir` change?" — and `corpusDrift()` answers that with one `stat` per document,
|
|
661
|
-
// reading nothing. Only the files whose stamp moved get re-ingested, which is what makes this
|
|
662
|
-
// cheap enough to run after EVERY tool result.
|
|
663
|
-
let reconciling = false;
|
|
664
|
-
// THROTTLE, with a trailing run. Drift checks run after EVERY tool result — including other
|
|
665
|
-
// plugins' — so on a busy session a full stat pass plus a recursive readdir would fire many times
|
|
666
|
-
// a second. Skipping alone would risk dropping the LAST edit (the one that matters), so a
|
|
667
|
-
// throttled call schedules exactly one trailing check instead of being discarded.
|
|
668
|
-
let lastDriftAt = 0;
|
|
669
|
-
let trailingDrift = null;
|
|
670
|
-
const reconcile = async (full) => {
|
|
671
|
-
if (reconciling)
|
|
672
|
-
return;
|
|
673
|
-
if (!full) {
|
|
674
|
-
const wait = RECONCILE_MIN_INTERVAL_MS - (Date.now() - lastDriftAt);
|
|
675
|
-
if (wait > 0) {
|
|
676
|
-
if (trailingDrift === null) {
|
|
677
|
-
trailingDrift = setTimeout(() => { trailingDrift = null; void reconcile(false); }, wait);
|
|
678
|
-
}
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
lastDriftAt = Date.now();
|
|
682
|
-
}
|
|
683
|
-
reconciling = true;
|
|
684
|
-
try {
|
|
685
|
-
if (full) {
|
|
686
|
-
await rt.knowledge.sync({});
|
|
687
|
-
// Take the baseline AFTER the sync: seeding it before would hide an edit that landed in
|
|
688
|
-
// between, which is the one window this whole mechanism exists to close.
|
|
689
|
-
rt.knowledge.corpusDrift();
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
const drift = rt.knowledge.corpusDrift();
|
|
693
|
-
if (drift.changed.length === 0 && drift.missing.length === 0 && !drift.fileSetChanged)
|
|
694
|
-
return;
|
|
695
|
-
if (drift.fileSetChanged || drift.missing.length > 0) {
|
|
696
|
-
// A new, vanished or frontmatter-destroyed `.md` is a corpus-level question (orphan /
|
|
697
|
-
// missing), not a doc-level one — so escalate to the full reconcile, which is the surface
|
|
698
|
-
// that reports it honestly instead of silently leaving the document un-ingested.
|
|
699
|
-
await rt.knowledge.sync({});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
for (const docId of drift.changed)
|
|
703
|
-
await rt.knowledge.sync({ docId });
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
logger.warn(`知识库自动同步失败(不影响已有内容):${error instanceof Error ? error.message : String(error)}`);
|
|
707
|
-
}
|
|
708
|
-
finally {
|
|
709
|
-
reconciling = false;
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
// Once at mount, for whatever changed while the host was not running; then after every tool call.
|
|
713
|
-
void reconcile(true);
|
|
714
|
-
ctx.on('tools/result', () => {
|
|
715
|
-
void reconcile(false);
|
|
716
|
-
});
|
|
717
|
-
for (const spec of toolSpecs) {
|
|
718
|
-
ctx.tools.register(defineTool({
|
|
719
|
-
name: spec.name,
|
|
720
|
-
description: spec.description,
|
|
721
|
-
// Structurally a ParameterSchemaSpec; the derivation lives in `tool_schema.ts`
|
|
722
|
-
// so it can be unit-tested without a harness checkout.
|
|
723
|
-
parameters: flattenToolSpec(spec),
|
|
724
|
-
output: OUTPUT,
|
|
725
|
-
execute: args => runToolSpec(spec, rt, args),
|
|
726
|
-
presentCall: (args) => present(spec, args),
|
|
727
|
-
}));
|
|
728
|
-
logger.info(`tool registered: ${spec.name}`);
|
|
729
|
-
}
|
|
730
|
-
new AvantfMemGateway(ctx, rt); // binder for the client `ctx.remote.avantfMem.*` bridge
|
|
731
|
-
logger.info('remote registered: avantfMem (client ctx.remote.avantfMem.*)');
|
|
732
|
-
// Strict host wire face: hand-written descriptors registered through the
|
|
733
|
-
// documented manual path (see src/remote.ts). Without this the gateway falls
|
|
734
|
-
// back to SRC discovery, which needs the generator workspace.
|
|
735
|
-
const disposeTypert = ctx.typert.register(hostContribution);
|
|
736
|
-
logger.info('typert registered: avantfMem host face (5 tools + 5 UI-only)');
|
|
737
|
-
// Post-registration health check: every REAL schema key (and every real tool name) must have
|
|
738
|
-
// landed, matched by exact key — never by a bare `list()` total, which a not-yet-withdrawn probe
|
|
739
|
-
// could inflate. A failure here is a WARNING only; the plugin is already mounted, so there is no
|
|
740
|
-
// clean "do not load" left to take. Skipped when the gate itself could not run (no base).
|
|
741
|
-
if (compat !== undefined) {
|
|
742
|
-
verifyRegisteredFaces({ ctx, toolNames: toolSpecs.map(spec => spec.name), log: logger, compat });
|
|
743
|
-
}
|
|
744
|
-
ctx.effect(() => () => {
|
|
745
|
-
logger.info('plugin unmount: disposing runtime (closing databases)');
|
|
746
|
-
rt.shutdown();
|
|
747
|
-
void disposeTypert();
|
|
748
|
-
// Release the framework's leases (and drop its per-process cache) with the fiber: a disposed
|
|
749
|
-
// plugin must not keep a managed copy marked as in use.
|
|
750
|
-
env?.dispose();
|
|
751
|
-
logger.info('plugin unmount: runtime disposed');
|
|
752
|
-
});
|
|
753
|
-
logger.info(`plugin ready: ${toolSpecs.length} tools + avantfMemory service + avantfMem remote`);
|
|
754
|
-
}
|
|
755
|
-
//# sourceMappingURL=index.js.map
|