@elvisw/dsh-magic-context 0.0.0-bootstrap.0 → 0.0.0-stage
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 +2 -452
- package/package.json +4 -108
- package/LICENSE +0 -23
- package/NOTICE +0 -93
- package/README.zh-CN.md +0 -368
- package/cordis.patch.yml +0 -20
- package/lib/client.js +0 -2270
- package/lib/core/compartments.js +0 -424
- package/lib/core/corpus-store.js +0 -176
- package/lib/core/directive-detect.js +0 -147
- package/lib/core/domain-reconcile.js +0 -394
- package/lib/core/domains.js +0 -127
- package/lib/core/embedding-local.js +0 -247
- package/lib/core/global-autostage.js +0 -223
- package/lib/core/global-domains.js +0 -302
- package/lib/core/journal.js +0 -248
- package/lib/core/llm-deadline.js +0 -53
- package/lib/core/machine-domain.js +0 -106
- package/lib/core/maintenance.js +0 -380
- package/lib/core/memory-bridge.js +0 -818
- package/lib/core/memory-categories.js +0 -84
- package/lib/core/memory-scoring.js +0 -65
- package/lib/core/memory-search.js +0 -414
- package/lib/core/memory-write.js +0 -1176
- package/lib/core/policy.js +0 -111
- package/lib/core/profile-confirmation.js +0 -55
- package/lib/core/profile-review.js +0 -375
- package/lib/core/profile-write.js +0 -283
- package/lib/core/reference-seeds.js +0 -63
- package/lib/core/route-health.js +0 -106
- package/lib/core/stores.js +0 -1021
- package/lib/core/tagger.js +0 -7
- package/lib/core/types.js +0 -15
- package/lib/core/vector-backfill.js +0 -140
- package/lib/core/vector-lane.js +0 -184
- package/lib/dsh/command-surface.js +0 -376
- package/lib/dsh/compaction-engine.js +0 -231
- package/lib/dsh/config-switches.js +0 -56
- package/lib/dsh/config.js +0 -13
- package/lib/dsh/context-pressure.js +0 -36
- package/lib/dsh/drop-landing.js +0 -42
- package/lib/dsh/emergency.js +0 -89
- package/lib/dsh/flush-command.js +0 -148
- package/lib/dsh/global-review.js +0 -412
- package/lib/dsh/historian.js +0 -907
- package/lib/dsh/live-journal.js +0 -81
- package/lib/dsh/maintenance-wiring.js +0 -229
- package/lib/dsh/management-pass.js +0 -564
- package/lib/dsh/memory-lane.js +0 -535
- package/lib/dsh/pre-step.js +0 -675
- package/lib/dsh/project-drift.js +0 -116
- package/lib/dsh/recomp.js +0 -192
- package/lib/dsh/reduce-guidance.js +0 -188
- package/lib/dsh/seams.js +0 -588
- package/lib/dsh/search-hint.js +0 -186
- package/lib/dsh/session-notice.js +0 -100
- package/lib/dsh/status-command.js +0 -75
- package/lib/dsh/telemetry.js +0 -349
- package/lib/dsh/tool-bridge.js +0 -1009
- package/lib/dsh/volatile.js +0 -89
- package/lib/index.js +0 -931
- package/tools/migrate-profile-package-name.mjs +0 -317
- package/vendor/onnx/LICENSE +0 -27
- package/vendor/onnx/ort-wasm-simd-threaded.mjs +0 -59
- package/vendor/onnx/ort-wasm-simd-threaded.wasm +0 -0
- package/vendor/onnx/ort.node.min.mjs +0 -8
package/lib/index.js
DELETED
|
@@ -1,931 +0,0 @@
|
|
|
1
|
-
import { CONTEXT_WINDOW_EXCEEDED_CODE, DSH_EVENTS, enumerateSurfaceToolResults, probeDshSeams, readUsageFields, requestContextOf, tagToolResultContent, } from "./dsh/seams.js";
|
|
2
|
-
import { reclaimForEmergency } from "./dsh/emergency.js";
|
|
3
|
-
import { historianInFlight } from "./dsh/historian.js";
|
|
4
|
-
import { publishReadyCompartments, surfaceTokens } from "./dsh/management-pass.js";
|
|
5
|
-
import { defineCtxExpandTool, defineCtxReduceTool, defineCtxMemoryTool } from "./dsh/tool-bridge.js";
|
|
6
|
-
import { ReduceNudgeTracker } from "./dsh/reduce-guidance.js";
|
|
7
|
-
import { registerCtxStatusCommand } from "./dsh/status-command.js";
|
|
8
|
-
import { registerCtxFlushCommand } from "./dsh/flush-command.js";
|
|
9
|
-
import { registerCtxRecompCommand } from "./dsh/recomp.js";
|
|
10
|
-
import { registerCtxGlobalCommand, registerCtxProfileCommand } from "./dsh/command-surface.js";
|
|
11
|
-
import { reviewGlobalCandidates } from "./dsh/global-review.js";
|
|
12
|
-
import { promoteMemoryToCandidate } from "./core/global-domains.js";
|
|
13
|
-
import { SearchHintTracker } from "./dsh/search-hint.js";
|
|
14
|
-
import { registerMagicCompactionEngine } from "./dsh/compaction-engine.js";
|
|
15
|
-
import { liveJournal } from "./dsh/live-journal.js";
|
|
16
|
-
import { createTelemetryService } from "./dsh/telemetry.js";
|
|
17
|
-
import { openMemoryLane } from "./dsh/memory-lane.js";
|
|
18
|
-
import { injectReduceGuidance, preStepListener } from "./dsh/pre-step.js";
|
|
19
|
-
import { wireMaintenance } from "./dsh/maintenance-wiring.js";
|
|
20
|
-
import { SWITCH_SNAPSHOT_META_KEY, buildSwitchSnapshot, diffSwitches } from "./dsh/config-switches.js";
|
|
21
|
-
import { isQuotaExhaustedDetail, RouteHealth } from "./core/route-health.js";
|
|
22
|
-
import { legacyTagsRetentionWarning, } from "./dsh/config.js";
|
|
23
|
-
import { DEFAULT_POLICY } from "./core/policy.js";
|
|
24
|
-
import z from '@deepseek-ai/schemastery';
|
|
25
|
-
import { ensureTag, tagPrefix } from "./core/tagger.js";
|
|
26
|
-
import { recoverOrphanHistorianRuns } from "./core/journal.js";
|
|
27
|
-
import { defaultDataDir, openStores, SCHEMA_VERSION } from "./core/stores.js";
|
|
28
|
-
import { errText } from "./core/types.js";
|
|
29
|
-
import { liveBoolean, liveNumber, liveRoute, liveString, policyOf } from "./dsh/volatile.js";
|
|
30
|
-
import { pressureUsedTokens, shouldLogContextPressure, } from "./dsh/context-pressure.js";
|
|
31
|
-
export const name = 'magic-context-dsh-poc';
|
|
32
|
-
/**
|
|
33
|
-
* Required host services. Declaring the injection makes the loader wait for
|
|
34
|
-
* availability (a bare `ctx.get()` does not) and unlocks guarded property
|
|
35
|
-
* access inside apply. A host without a tools registry or LLM seam pends this
|
|
36
|
-
* plugin visibly instead of half-mounting it.
|
|
37
|
-
*/
|
|
38
|
-
export const inject = ['tools', 'llm'];
|
|
39
|
-
/** One legacy-config note per process, not one per mount. */
|
|
40
|
-
let legacyTagsWarningEmitted = false;
|
|
41
|
-
export async function apply(ctx, config = {}) {
|
|
42
|
-
if (config.enabled === false) {
|
|
43
|
-
ctx.logger.info('magic-context dsh adapter: disabled by config');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const probe = probeDshSeams(ctx);
|
|
47
|
-
if (!probe.ok) {
|
|
48
|
-
// Fail loud, not silent: an adapter that cannot see its seams must not
|
|
49
|
-
// pretend to manage context (Magic Context's fail-closed discipline).
|
|
50
|
-
throw new Error(`magic-context dsh adapter: DSH seam probe failed on this host build: ${probe.problems.join('; ')}`);
|
|
51
|
-
}
|
|
52
|
-
const policy = policyOf(config.policy);
|
|
53
|
-
/** Re-evaluated per pass so a settings-form edit applies immediately. */
|
|
54
|
-
const activePolicy = () => policyOf(config.policy);
|
|
55
|
-
/**
|
|
56
|
-
* The generation pin (historian + `/compact` summaries), re-read at every use
|
|
57
|
-
* point so a settings-form edit reaches the next run without a remount.
|
|
58
|
-
* `undefined` = follow the session's own route (the pre-step lane passes it
|
|
59
|
-
* as the fallback; the engine/command lanes pass no override).
|
|
60
|
-
*/
|
|
61
|
-
const historianRouteOf = () => liveRoute(config.historian?.provider, config.historian?.model);
|
|
62
|
-
/**
|
|
63
|
-
* Effort is independent of the route pin: it must also apply when the
|
|
64
|
-
* historian follows the session route. Each lane merges it with the route it
|
|
65
|
-
* actually resolved (the pre-step has the live session route; the engine and
|
|
66
|
-
* /ctx-recomp merge it into their active/fallback route).
|
|
67
|
-
*/
|
|
68
|
-
const historianEffortOf = () => {
|
|
69
|
-
const effort = liveString(config.historian?.reasoningEffort, '').trim();
|
|
70
|
-
return effort === '' ? undefined : effort;
|
|
71
|
-
};
|
|
72
|
-
/** Process start, for the telemetry uptime readout. */
|
|
73
|
-
const startedAt = Date.now();
|
|
74
|
-
const storeDir = config.dataDir ?? defaultDataDir();
|
|
75
|
-
const stores = openStores(storeDir);
|
|
76
|
-
ctx.logger.info(`magic-context dsh adapter: stores at ${stores.dbPath}`);
|
|
77
|
-
// §N§ tags are not deleted by age. The fork's mount-time 30-day prune is
|
|
78
|
-
// gone; message/session deletion and compaction are the original model's
|
|
79
|
-
// retention paths, but this adapter does not implement deletion here.
|
|
80
|
-
const legacyTagsWarning = legacyTagsRetentionWarning(config);
|
|
81
|
-
if (legacyTagsWarning !== undefined && !legacyTagsWarningEmitted) {
|
|
82
|
-
legacyTagsWarningEmitted = true;
|
|
83
|
-
ctx.logger.warn(legacyTagsWarning);
|
|
84
|
-
}
|
|
85
|
-
// Volatile config leaves arrive as live references (see `Config`), so every
|
|
86
|
-
// consumer reads through the `live*` helpers instead of comparing the raw
|
|
87
|
-
// value: `config.registerCompactionEngine !== false` is TRUE for a reference,
|
|
88
|
-
// which would mount the engine a user had explicitly disabled.
|
|
89
|
-
//
|
|
90
|
-
// `journal` is a LIVE wrapper when the composition boots with the journal
|
|
91
|
-
// on: enabled/level/mirror/retention are read at write time, so a
|
|
92
|
-
// settings-form edit applies to the next entry (a volatile commit never
|
|
93
|
-
// remounts, and the old mount-time construction made every journal edit a
|
|
94
|
-
// dead option until restart — the audit, 2026-10-10). Booting with
|
|
95
|
-
// `journal.enabled: false` still wires `undefined`: the maintenance tick
|
|
96
|
-
// reads a missing journal as "do not run", and the boot-time opt-out keeps
|
|
97
|
-
// that meaning (turning it back on then needs an app restart, which the
|
|
98
|
-
// form's hint says).
|
|
99
|
-
const journal = liveBoolean(config.journal?.enabled, true) === false
|
|
100
|
-
? undefined
|
|
101
|
-
: liveJournal({ config, stores, storeDir, logger: ctx.logger });
|
|
102
|
-
/** Sessions already announced, so the journal gets exactly one first-seen row. */
|
|
103
|
-
const seenSessions = new Set();
|
|
104
|
-
// Journal repair: an hmr remount disposes stores mid-run and leaves a
|
|
105
|
-
// historian `start` without a terminal sibling. At mount time no run of the
|
|
106
|
-
// new composition is in flight yet, so every still-open start predates this
|
|
107
|
-
// process and gets one synthetic `adapter-remount` terminal (journal.ts).
|
|
108
|
-
const recoveredHistorianRuns = journal === undefined ? 0 : recoverOrphanHistorianRuns(stores, journal);
|
|
109
|
-
/** Resolved tool-constructor path, recorded at boot for the review. */
|
|
110
|
-
let toolMode = 'skipped';
|
|
111
|
-
/** The LLM seam (declared inject); the historian's auxiliary call uses it. */
|
|
112
|
-
const llm = ctx.llm;
|
|
113
|
-
/** Reduce-guidance state per session (Channel 1/2 sticky bands, turn counts). */
|
|
114
|
-
const reduceTracker = new ReduceNudgeTracker();
|
|
115
|
-
// ── The memory lane ────────────────────────────────────────────────────────
|
|
116
|
-
// `memory` is deliberately NOT declared in the exported Config: a schemastery
|
|
117
|
-
// key would be materialized into every profile. It arrives as a plain object,
|
|
118
|
-
// and since the distribution defaults (0.9.0) the gate is:
|
|
119
|
-
// absent config → on (defaults; corpus beside the adapter store)
|
|
120
|
-
// enabled: false → off (explicit opt-out; reported as disabled-by-config)
|
|
121
|
-
// anything else → on
|
|
122
|
-
// The bundle's presence in `dsh.profile.bundles` IS the consent: after
|
|
123
|
-
// `dsh plugin add` the features must work with no profile hand-editing.
|
|
124
|
-
// Read once, inside the lane, so a disabled adapter can never half-open.
|
|
125
|
-
const lane = openMemoryLane(ctx, config, stores);
|
|
126
|
-
const { memoryRecall, memoryWriter, profileWriter, memorySearch, describeMemoryWrite } = lane;
|
|
127
|
-
const { memorySettings } = lane;
|
|
128
|
-
const disposers = [];
|
|
129
|
-
const on = (name, listener) => {
|
|
130
|
-
disposers.push(ctx.on.call(ctx, name, listener));
|
|
131
|
-
};
|
|
132
|
-
// ── The compaction seam ────────────────────────────────────────────────────
|
|
133
|
-
// Occupying `ctx.compaction` is what makes this adapter authoritative over
|
|
134
|
-
// reclaim. The seam is FIRST-COME-FIRST-SERVED — measured on the real host
|
|
135
|
-
// stack (tests/compaction-conflict.spec.ts, 2026-10-14): when compaction-
|
|
136
|
-
// basic mounts first (the stock composition — the base bundle ships it),
|
|
137
|
-
// OUR engine's registration is rejected and the adapter stands down (warn
|
|
138
|
-
// log + `unavailable:` meta; compaction-basic keeps compaction; everything
|
|
139
|
-
// else about the adapter is unaffected). When the adapter mounts first,
|
|
140
|
-
// compaction-basic cannot mount at all. Takeover therefore needs a profile
|
|
141
|
-
// that does not mount compaction-basic — defaulting the switch on cannot
|
|
142
|
-
// win on stock and stays opt-in.
|
|
143
|
-
// `registerCompactionEngine` is a LIVE switch: the pre-step band calls
|
|
144
|
-
// `reconcileEngine` on every step, so a settings-form toggle mounts or
|
|
145
|
-
// unmounts the engine without a restart. The switch is volatile, and a
|
|
146
|
-
// volatile commit never remounts the plugin — the old mount-time branch
|
|
147
|
-
// made the form's toggle a dead option: it saved, and nothing changed
|
|
148
|
-
// until the app restarted (the audit, 2026-10-10). The state sits here,
|
|
149
|
-
// before any `await` in apply, so a pre-step firing mid-apply (during the
|
|
150
|
-
// tools section's dynamic import) cannot hit a temporal-dead-zone read.
|
|
151
|
-
let adapterDisposed = false;
|
|
152
|
-
let engineHandle;
|
|
153
|
-
let engineBusy = false;
|
|
154
|
-
/** One mount attempt per ON transition: a failing mount must not retry on every step. */
|
|
155
|
-
let engineFailed = false;
|
|
156
|
-
const reconcileEngine = () => {
|
|
157
|
-
const wanted = liveBoolean(config.registerCompactionEngine, false);
|
|
158
|
-
if (engineBusy || adapterDisposed || wanted === (engineHandle !== undefined))
|
|
159
|
-
return;
|
|
160
|
-
if (wanted && engineFailed)
|
|
161
|
-
return;
|
|
162
|
-
engineBusy = true;
|
|
163
|
-
if (wanted) {
|
|
164
|
-
void (async () => {
|
|
165
|
-
try {
|
|
166
|
-
const base = config.compactionBase ?? (await import('@deepseek-ai/dsh-compaction')).CompactionEngine;
|
|
167
|
-
// Awaiting: a duplicate-mount rejection (`ctx.compaction` already
|
|
168
|
-
// owned) must land in the catch below, not become an unhandled
|
|
169
|
-
// rejection.
|
|
170
|
-
engineHandle = await registerMagicCompactionEngine(ctx, {
|
|
171
|
-
base,
|
|
172
|
-
stores,
|
|
173
|
-
policy,
|
|
174
|
-
activePolicy,
|
|
175
|
-
logger: ctx.logger,
|
|
176
|
-
...llm === undefined ? {} : { llm },
|
|
177
|
-
...journal === undefined ? {} : { journal },
|
|
178
|
-
activeRoute: historianRouteOf,
|
|
179
|
-
activeEffort: historianEffortOf,
|
|
180
|
-
...memoryRecall === undefined ? {} : { recall: memoryRecall },
|
|
181
|
-
isDisposed: () => adapterDisposed,
|
|
182
|
-
});
|
|
183
|
-
ctx.logger.info('magic-context dsh adapter: CompactionEngine mounted on ctx.compaction');
|
|
184
|
-
stores.setMeta('compaction_engine', 'magic-context-dsh');
|
|
185
|
-
journal?.info('engine', 'CompactionEngine mounted on ctx.compaction', { action: 'mounted' });
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
const detail = errText(error);
|
|
189
|
-
engineFailed = true;
|
|
190
|
-
stores.setMeta('compaction_engine', `unavailable: ${detail}`);
|
|
191
|
-
journal?.warn('engine', `CompactionEngine not mounted: ${detail}`, { action: 'unavailable', detail });
|
|
192
|
-
ctx.logger.warn(`magic-context dsh adapter: CompactionEngine not mounted `
|
|
193
|
-
+ `(${detail}); the management loop is unaffected`);
|
|
194
|
-
}
|
|
195
|
-
finally {
|
|
196
|
-
engineBusy = false;
|
|
197
|
-
}
|
|
198
|
-
})();
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const handle = engineHandle;
|
|
202
|
-
if (handle === undefined) {
|
|
203
|
-
// Unreachable past the guard above; kept so the narrowing is explicit.
|
|
204
|
-
engineBusy = false;
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
engineHandle = undefined;
|
|
208
|
-
engineFailed = false;
|
|
209
|
-
void handle.dispose()
|
|
210
|
-
.then(() => {
|
|
211
|
-
// Record the opt-out explicitly: a stale "mounted" value from an
|
|
212
|
-
// earlier composition would otherwise keep claiming the seam.
|
|
213
|
-
stores.setMeta('compaction_engine', 'disabled-by-config');
|
|
214
|
-
journal?.info('engine', 'CompactionEngine unmounted by configuration', { action: 'unmounted' });
|
|
215
|
-
ctx.logger.info('magic-context dsh adapter: CompactionEngine unmounted (disabled by configuration)');
|
|
216
|
-
})
|
|
217
|
-
.catch((error) => {
|
|
218
|
-
ctx.logger.warn(`magic-context dsh adapter: engine unmount failed: ${errText(error)}`);
|
|
219
|
-
})
|
|
220
|
-
.finally(() => { engineBusy = false; });
|
|
221
|
-
};
|
|
222
|
-
// Boot: mount eagerly, or record the opt-out explicitly (a stale "mounted"
|
|
223
|
-
// value from an earlier composition would otherwise keep claiming the seam).
|
|
224
|
-
if (liveBoolean(config.registerCompactionEngine, false)) {
|
|
225
|
-
reconcileEngine();
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
stores.setMeta('compaction_engine', 'disabled-by-config');
|
|
229
|
-
journal?.info('engine', 'CompactionEngine disabled by configuration', { action: 'disabled-by-config' });
|
|
230
|
-
}
|
|
231
|
-
// Identities the preStep registers, the maintenance sweep serves and the
|
|
232
|
-
// panel scopes by — one registry, three consumers.
|
|
233
|
-
const projects = { seenIdentities: new Set(), sessionIdentity: new Map() };
|
|
234
|
-
// Auto-search hint state: one hint per user turn (preStep runs per model step).
|
|
235
|
-
const searchHints = new SearchHintTracker();
|
|
236
|
-
// The main conversation's latest route (last-seen-wins across sessions) and
|
|
237
|
-
// the volatile per-provider quota observations fed from `agent/request-error`.
|
|
238
|
-
// The maintenance lane follows the route when unpinned, and skips its LLM
|
|
239
|
-
// steps while the route's latest observation is an exhausted quota — the
|
|
240
|
-
// signal is owned by real traffic and never persisted, so an externally
|
|
241
|
-
// reset quota cannot be trapped by a stale guess.
|
|
242
|
-
let lastSessionRoute;
|
|
243
|
-
const routeHealth = new RouteHealth();
|
|
244
|
-
// ── §N§ tags + the pressure decision on the admitted user batch ───────────
|
|
245
|
-
on(DSH_EVENTS.preStep, preStepListener({
|
|
246
|
-
ctx,
|
|
247
|
-
config,
|
|
248
|
-
stores,
|
|
249
|
-
journal,
|
|
250
|
-
llm,
|
|
251
|
-
reduceTracker,
|
|
252
|
-
lane,
|
|
253
|
-
projects,
|
|
254
|
-
seenSessions,
|
|
255
|
-
activePolicy,
|
|
256
|
-
/** Engine mount/unmount rides the step band: a form toggle applies without a restart. */
|
|
257
|
-
reconcileEngine: () => reconcileEngine(),
|
|
258
|
-
searchHints,
|
|
259
|
-
noteSessionRoute: route => { lastSessionRoute = route; },
|
|
260
|
-
}));
|
|
261
|
-
// ── §N§ tags on tool results at landing (Phase 0) ─────────────────────────
|
|
262
|
-
on(DSH_EVENTS.postToolExecute, async (exec, result, next) => {
|
|
263
|
-
const decision = await next();
|
|
264
|
-
if (decision.kind !== 'accept' || 'value' in decision || result.isError)
|
|
265
|
-
return decision;
|
|
266
|
-
const sessionId = exec.agent?.session.id;
|
|
267
|
-
if (sessionId === undefined)
|
|
268
|
-
return decision;
|
|
269
|
-
const tag = ensureTag(stores, sessionId, { kind: 'tool-result', callId: exec.callId });
|
|
270
|
-
const base = decision.content ?? result.content;
|
|
271
|
-
// Nothing to tag (a result without content blocks); the decision is
|
|
272
|
-
// already the host's, so it passes through unchanged.
|
|
273
|
-
if (base === undefined)
|
|
274
|
-
return decision;
|
|
275
|
-
return { kind: 'accept', content: tagToolResultContent(base, tagPrefix, tag) };
|
|
276
|
-
});
|
|
277
|
-
// ── Reduce guidance through the SystemPrompt service (MC parity) ──────────
|
|
278
|
-
injectReduceGuidance({ ctx, config, stores, journal, llm, reduceTracker, lane, projects, seenSessions, activePolicy });
|
|
279
|
-
// ── Usage recording from durable assistant/message events (Phase 0) ───────
|
|
280
|
-
on(DSH_EVENTS.sessionEvent, (session, event) => {
|
|
281
|
-
const fields = readUsageFields(event);
|
|
282
|
-
if (fields === undefined)
|
|
283
|
-
return;
|
|
284
|
-
const record = { sessionId: session.id, recordedAt: Date.now(), ...fields };
|
|
285
|
-
try {
|
|
286
|
-
stores.recordUsage(record);
|
|
287
|
-
}
|
|
288
|
-
catch (error) {
|
|
289
|
-
const detail = errText(error);
|
|
290
|
-
journal?.warn('error', `usage recording failed: ${detail}`, { scope: 'usage', detail }, session.id);
|
|
291
|
-
// Best effort by design (mirrors MC per-turn fail-open): the next
|
|
292
|
-
// assistant/message anchors a fresh row.
|
|
293
|
-
ctx.logger.warn(`magic-context: usage recording failed for ${session.id}@${String(fields.seq)}: ${detail}`);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
// ── Emergency reclaim + retry on provider-confirmed overflow ──────────────
|
|
297
|
-
on(DSH_EVENTS.requestError, async (payload, next) => {
|
|
298
|
-
// Route-health feed, best-effort by design: these reads sit BEFORE the
|
|
299
|
-
// band try/catch below, so a drifted payload shape would throw into the
|
|
300
|
-
// host's request-error waterfall. An exhausted-quota wording for a known
|
|
301
|
-
// provider arms the maintenance lane's skip; anything else is ignored.
|
|
302
|
-
try {
|
|
303
|
-
const provider = payload?.provider;
|
|
304
|
-
const failure = payload?.failure;
|
|
305
|
-
if (typeof provider === 'string' && provider.length > 0 && failure !== undefined) {
|
|
306
|
-
const detail = `${failure.code ?? ''} ${failure.message ?? ''}`;
|
|
307
|
-
if (isQuotaExhaustedDetail(detail))
|
|
308
|
-
routeHealth.observeQuotaFailure(provider, Date.now());
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
catch (feedError) {
|
|
312
|
-
ctx.logger.warn(`magic-context: route-health feed failed: ${errText(feedError)}`);
|
|
313
|
-
}
|
|
314
|
-
// Shape-tolerant guard: these reads sit BEFORE the band try/catch below, so
|
|
315
|
-
// a drifted payload shape would throw into the host's request-error
|
|
316
|
-
// waterfall. Degrade to pass-through instead — the emergency lane simply
|
|
317
|
-
// stays idle, and the doctor heartbeat is what surfaces the drift.
|
|
318
|
-
if (payload?.failure?.code !== CONTEXT_WINDOW_EXCEEDED_CODE || payload?.signal?.aborted)
|
|
319
|
-
return next();
|
|
320
|
-
const session = payload.agent?.session;
|
|
321
|
-
const sessionId = session?.id;
|
|
322
|
-
if (session === undefined || sessionId === undefined)
|
|
323
|
-
return next();
|
|
324
|
-
const before = session.surface.replaceGeneration;
|
|
325
|
-
try {
|
|
326
|
-
// C1: the token window scales with the route's context window.
|
|
327
|
-
const context = requestContextOf(session);
|
|
328
|
-
// ── Tier 0 (fusion, mon3stera's ready bank): land ready compartments
|
|
329
|
-
// BEFORE the drop lane. One replacement reclaims a whole digested
|
|
330
|
-
// span; an emergency drop only individual tool results — and the
|
|
331
|
-
// request has already failed once, so this bust cannot lose more than
|
|
332
|
-
// the failed retry did. This lane is a forced execute band in
|
|
333
|
-
// everything but name (the same exception family as the wire-repair
|
|
334
|
-
// iron rule: the provider rejected the wire as-is). Fail-open: a
|
|
335
|
-
// publish failure falls through to the drop lane, never skips it. No
|
|
336
|
-
// baseline refresh and no fact promotion here — both belong to the
|
|
337
|
-
// next execute pass (facts stay durable in the compartment row).
|
|
338
|
-
const publishedIds = [];
|
|
339
|
-
const publishOnce = () => {
|
|
340
|
-
const bank = publishReadyCompartments({
|
|
341
|
-
session,
|
|
342
|
-
sessionId,
|
|
343
|
-
stores,
|
|
344
|
-
policy,
|
|
345
|
-
...journal === undefined ? {} : { journal },
|
|
346
|
-
});
|
|
347
|
-
publishedIds.push(...bank.ids);
|
|
348
|
-
};
|
|
349
|
-
publishOnce();
|
|
350
|
-
// ── Tier 0.5 (OFF by default): a digest in flight gets a bounded
|
|
351
|
-
// chance to finish before the drop lane fires (mon3stera's
|
|
352
|
-
// waitReadyTimeout race). `forceWaitReadyMs: 0` — the shipped default —
|
|
353
|
-
// skips the wait; flip it after the force-path observation window.
|
|
354
|
-
if (publishedIds.length === 0) {
|
|
355
|
-
const waitReadyMs = Math.max(0, policy.forceWaitReadyMs);
|
|
356
|
-
const inFlight = historianInFlight(sessionId);
|
|
357
|
-
if (waitReadyMs > 0 && inFlight !== undefined) {
|
|
358
|
-
await Promise.race([
|
|
359
|
-
inFlight.catch(() => undefined),
|
|
360
|
-
new Promise(resolve => {
|
|
361
|
-
const timer = setTimeout(resolve, waitReadyMs);
|
|
362
|
-
timer.unref?.();
|
|
363
|
-
}),
|
|
364
|
-
]);
|
|
365
|
-
journal?.info('force-path', `overflow path waited on the in-flight historian (${String(waitReadyMs)}ms cap)`, {
|
|
366
|
-
tier: 'wait',
|
|
367
|
-
waitReadyMs,
|
|
368
|
-
}, sessionId);
|
|
369
|
-
publishOnce();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (publishedIds.length > 0) {
|
|
373
|
-
journal?.warn('force-path', `provider rejected the request as too long; landed ${String(publishedIds.length)} ready compartment(s) before the drop lane`, {
|
|
374
|
-
tier: 'publish',
|
|
375
|
-
published: publishedIds,
|
|
376
|
-
reason: 'context-window-exceeded',
|
|
377
|
-
generationBefore: before,
|
|
378
|
-
generationAfter: session.surface.replaceGeneration,
|
|
379
|
-
}, sessionId);
|
|
380
|
-
}
|
|
381
|
-
// Re-enumerate AFTER any publish: it shadows surface nodes, and
|
|
382
|
-
// dropping against a stale seq would throw into the waterfall.
|
|
383
|
-
const nodes = enumerateSurfaceToolResults(session, callId => stores.getTag(sessionId, { kind: 'tool-result', callId }));
|
|
384
|
-
// #731: the whole-surface estimate (measured AFTER any Tier-0 publish)
|
|
385
|
-
// lets the drop lane refuse a reclaim that cannot fit the request.
|
|
386
|
-
const reclaim = reclaimForEmergency(session, nodes, policy, context.contextWindow, surfaceTokens(session));
|
|
387
|
-
journal?.warn('emergency-reclaim', `provider rejected the request as too long; dropped ${String(reclaim.dropped.length)} tool output(s)`, {
|
|
388
|
-
dropped: reclaim.dropped.length,
|
|
389
|
-
protected: reclaim.protectedCount,
|
|
390
|
-
reason: 'context-window-exceeded',
|
|
391
|
-
tier: reclaim.skipped === 'cannot-fit'
|
|
392
|
-
? 'skipped-cannot-fit'
|
|
393
|
-
: reclaim.dropped.length > 0 ? 'drop' : publishedIds.length > 0 ? 'publish-only' : 'none',
|
|
394
|
-
// #731: dropping everything reclaimable still leaves residualTokens
|
|
395
|
-
// against the window — the results are preserved and no retry fires
|
|
396
|
-
// (no drop ⇒ no surface progress ⇒ `next()` passes the error through).
|
|
397
|
-
...(reclaim.skipped === 'cannot-fit'
|
|
398
|
-
? { skipped: reclaim.skipped, residualTokens: reclaim.residualTokens, contextWindow: context.contextWindow }
|
|
399
|
-
: {}),
|
|
400
|
-
publishedBeforeDrop: publishedIds,
|
|
401
|
-
generationBefore: before,
|
|
402
|
-
generationAfter: session.surface.replaceGeneration,
|
|
403
|
-
retried: session.surface.replaceGeneration > before,
|
|
404
|
-
}, sessionId);
|
|
405
|
-
ctx.logger.warn(`magic-context: overflow reclaim landed ${String(publishedIds.length)} compartment(s), dropped ${String(reclaim.dropped.length)} tool outputs `
|
|
406
|
-
+ `(${String(reclaim.protectedCount)} protected) after a context-overflow rejection`);
|
|
407
|
-
}
|
|
408
|
-
catch (error) {
|
|
409
|
-
const detail = errText(error);
|
|
410
|
-
journal?.error('error', `emergency reclaim failed: ${detail}`, { scope: 'request-error', detail }, sessionId);
|
|
411
|
-
ctx.logger.warn(`magic-context: emergency reclaim failed: ${detail}`);
|
|
412
|
-
return next();
|
|
413
|
-
}
|
|
414
|
-
// Durable surface progress is the retry proof (compaction-basic pattern):
|
|
415
|
-
// a retry re-derives the request from the shrunken surface.
|
|
416
|
-
if (!payload?.signal?.aborted && session.surface.replaceGeneration > before) {
|
|
417
|
-
return { kind: 'retry' };
|
|
418
|
-
}
|
|
419
|
-
return next();
|
|
420
|
-
});
|
|
421
|
-
// ── Tools + command ────────────────────────────────────────────────────────
|
|
422
|
-
// Human approval for global-domain candidates (L1① second leg): the asker
|
|
423
|
-
// resolves the session's live root Agent and forwards the cards to the
|
|
424
|
-
// host's user-questions waterfall. Structural injection — the package is
|
|
425
|
-
// not a dependency; the asker stays undefined (fail-closed at call time)
|
|
426
|
-
// when the service is absent, e.g. headless.
|
|
427
|
-
let askGlobalReview;
|
|
428
|
-
ctx.inject(['userQuestions', 'agents'], (scope) => {
|
|
429
|
-
const { userQuestions, agents } = scope;
|
|
430
|
-
if (userQuestions === undefined || agents === undefined)
|
|
431
|
-
return;
|
|
432
|
-
askGlobalReview = async ({ questions, sessionId }) => {
|
|
433
|
-
const agent = sessionId === undefined ? undefined : agents.get(sessionId);
|
|
434
|
-
return userQuestions.ask({ questions, ...(agent === undefined ? {} : { agent }) });
|
|
435
|
-
};
|
|
436
|
-
});
|
|
437
|
-
if (config.registerTools !== false) {
|
|
438
|
-
// Constructor resolution: host-injected → dynamic import. The import is
|
|
439
|
-
// the normal path (`@deepseek-ai/dsh-tools` is a hard dependency); when
|
|
440
|
-
// neither resolves, the failure propagates and the mount aborts loudly —
|
|
441
|
-
// the ctx_* tools are the only entry for the agent's reduce marks, so a
|
|
442
|
-
// boot without them must not half-run. See dsh/tool-bridge.ts.
|
|
443
|
-
let defineTool = config.defineTool;
|
|
444
|
-
toolMode = 'host-injected';
|
|
445
|
-
if (defineTool === undefined) {
|
|
446
|
-
defineTool = (await import('@deepseek-ai/dsh-tools')).defineTool;
|
|
447
|
-
toolMode = 'dynamic-import';
|
|
448
|
-
}
|
|
449
|
-
try {
|
|
450
|
-
const registrar = ctx.tools;
|
|
451
|
-
// The host's `defineTool` takes a wider option type than the subset this
|
|
452
|
-
// package authors; `tool-bridge`'s seam names that subset so the three
|
|
453
|
-
// tool bodies keep their argument types (see `LocalDefineTool`).
|
|
454
|
-
const deps = { stores, defineTool: defineTool, ...journal === undefined ? {} : { journal } };
|
|
455
|
-
registrar.register(defineCtxReduceTool(deps));
|
|
456
|
-
registrar.register(defineCtxExpandTool(deps));
|
|
457
|
-
if (memoryRecall !== undefined) {
|
|
458
|
-
registrar.register(defineCtxMemoryTool(lane.ctxMemoryRegistration({
|
|
459
|
-
activePolicy,
|
|
460
|
-
defineTool,
|
|
461
|
-
journal,
|
|
462
|
-
// L1① second leg: the review closure reads the asker binding lazily —
|
|
463
|
-
// the inject above may land after this registration runs.
|
|
464
|
-
reviewGlobal: exec => {
|
|
465
|
-
const ask = askGlobalReview;
|
|
466
|
-
const pending = reviewGlobalCandidates({
|
|
467
|
-
store: stores,
|
|
468
|
-
...(ask === undefined
|
|
469
|
-
? {}
|
|
470
|
-
: { ask: ({ questions, sessionId }) => ask({ questions, sessionId }) }),
|
|
471
|
-
...memoryWriter === undefined || memoryWriter.status.ok !== true ? {} : { publisher: memoryWriter },
|
|
472
|
-
...journal === undefined ? {} : { journal },
|
|
473
|
-
// Default 50 = the auto-stage pool cap: one review_global call
|
|
474
|
-
// drains the whole pending queue through chained 5-question
|
|
475
|
-
// cards (2026-10-13). The old call-site pin of 5 sliced a batch
|
|
476
|
-
// into one card per invocation.
|
|
477
|
-
batchLimit: liveNumber(config.memory?.global?.review?.batchLimit, 50),
|
|
478
|
-
sessionId: exec?.agent?.session?.id,
|
|
479
|
-
now: () => Date.now(),
|
|
480
|
-
});
|
|
481
|
-
return pending.then(r => ({
|
|
482
|
-
outcome: `审批完成:批准 ${String(r.approved)} / 改判 ${String(r.reclassified)} / 拒绝 ${String(r.rejected)}`
|
|
483
|
-
+ ` / 跳过 ${String(r.skipped)} / 失败 ${String(r.failed)};剩余待审 ${String(r.remaining)}`,
|
|
484
|
-
detail: r.lines.length === 0 ? undefined : r.lines.join('\n'),
|
|
485
|
-
}));
|
|
486
|
-
},
|
|
487
|
-
// L1① second leg, instant path: promote enqueues ONE project memory
|
|
488
|
-
// into a global domain and raises its card at once (onlyCandidateId)
|
|
489
|
-
// — the human-judged lane without the recurrence gate. Same asker
|
|
490
|
-
// binding as reviewGlobal: the lazy read survives the inject landing
|
|
491
|
-
// after this registration.
|
|
492
|
-
promoteGlobal: (input, exec) => {
|
|
493
|
-
if (memoryRecall === undefined) {
|
|
494
|
-
return Promise.resolve({ outcome: '共享记忆库未接线,无法提升。' });
|
|
495
|
-
}
|
|
496
|
-
const domainId = input.domain === undefined || input.domain === '' || input.domain === 'org'
|
|
497
|
-
? 'dir:global:org'
|
|
498
|
-
: input.domain === 'machine' ? 'dir:global:machine' : input.domain;
|
|
499
|
-
const promoted = promoteMemoryToCandidate(stores, {
|
|
500
|
-
corpusDbPath: memoryRecall.dbPath,
|
|
501
|
-
memoryId: input.memoryId,
|
|
502
|
-
domainId,
|
|
503
|
-
now: Date.now(),
|
|
504
|
-
});
|
|
505
|
-
if (!promoted.ok) {
|
|
506
|
-
return Promise.resolve({ outcome: `提升失败:${promoted.error}` });
|
|
507
|
-
}
|
|
508
|
-
const ask = askGlobalReview;
|
|
509
|
-
const review = reviewGlobalCandidates({
|
|
510
|
-
store: stores,
|
|
511
|
-
...(ask === undefined
|
|
512
|
-
? {}
|
|
513
|
-
: { ask: ({ questions, sessionId }) => ask({ questions, sessionId }) }),
|
|
514
|
-
...memoryWriter === undefined || memoryWriter.status.ok !== true ? {} : { publisher: memoryWriter },
|
|
515
|
-
...journal === undefined ? {} : { journal },
|
|
516
|
-
sessionId: exec?.agent?.session?.id,
|
|
517
|
-
now: () => Date.now(),
|
|
518
|
-
onlyCandidateId: promoted.candidate.id,
|
|
519
|
-
});
|
|
520
|
-
return review.then(r => ({
|
|
521
|
-
outcome: `候选 #${String(promoted.candidate.id)} 已入池(${domainId}),审批卡已弹出:`
|
|
522
|
-
+ `批准 ${String(r.approved)} / 改判 ${String(r.reclassified)} / 拒绝 ${String(r.rejected)}`
|
|
523
|
-
+ ` / 跳过 ${String(r.skipped)} / 失败 ${String(r.failed)}`,
|
|
524
|
-
detail: r.lines.length === 0 ? undefined : r.lines.join('\n'),
|
|
525
|
-
}));
|
|
526
|
-
},
|
|
527
|
-
})));
|
|
528
|
-
}
|
|
529
|
-
ctx.logger.info(`magic-context dsh adapter: ctx_reduce / ctx_expand registered (${toolMode})`);
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
// Fail closed: without its tools the adapter cannot accept the
|
|
533
|
-
// agent's reduce marks, so a mount without them must be loud, not
|
|
534
|
-
// silently half-alive. Use registerTools: false to opt out explicitly.
|
|
535
|
-
throw new Error(`magic-context dsh adapter: agent tools failed to register `
|
|
536
|
-
+ `(${errText(error)})`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (config.registerStatusCommand !== false) {
|
|
540
|
-
registerCtxStatusCommand(ctx, { stores, policy: activePolicy });
|
|
541
|
-
// Flush shares the slash-command gate: it is a manual queue lever, not a
|
|
542
|
-
// second recovery mode. The command only writes the durable marker; the
|
|
543
|
-
// next preStep lands it through the same helper as the automatic pass.
|
|
544
|
-
registerCtxFlushCommand(ctx, { stores });
|
|
545
|
-
// Recomp rides the same gate: it needs the same seams as the historian, and
|
|
546
|
-
// its handler refuses with a readable message when the LLM is absent or the
|
|
547
|
-
// historian is switched off.
|
|
548
|
-
registerCtxRecompCommand(ctx, {
|
|
549
|
-
stores,
|
|
550
|
-
policy: activePolicy,
|
|
551
|
-
logger: ctx.logger,
|
|
552
|
-
// LIVE gate: the same volatile `historianEnabled` the pre-step band
|
|
553
|
-
// reads at decision time (the old mount-time boolean disagreed with
|
|
554
|
-
// the live gate after a settings-form toggle).
|
|
555
|
-
enabled: () => liveBoolean(config.historianEnabled, true) !== false,
|
|
556
|
-
...journal === undefined ? {} : { journal },
|
|
557
|
-
...llm === undefined ? {} : { llm },
|
|
558
|
-
activeRoute: historianRouteOf,
|
|
559
|
-
activeEffort: historianEffortOf,
|
|
560
|
-
});
|
|
561
|
-
ctx.logger.info('magic-context dsh adapter: /ctx-status, /ctx-flush and /ctx-recomp registered');
|
|
562
|
-
// The global-domain surface rides the same gate: with Magic Context's
|
|
563
|
-
// dashboard gone these commands ARE the operating surface for the profile
|
|
564
|
-
// queue and the domains.
|
|
565
|
-
registerCtxProfileCommand(ctx, {
|
|
566
|
-
stores,
|
|
567
|
-
...memoryRecall === undefined ? {} : { corpusDbPath: memoryRecall.dbPath },
|
|
568
|
-
// WP12/DoD: retiring a profile row is the user domain's only write from
|
|
569
|
-
// the command surface. Without the writer the command says so instead of
|
|
570
|
-
// reporting a retirement that never happened.
|
|
571
|
-
...profileWriter === undefined || profileWriter.status.ok !== true ? {} : { profileWriter },
|
|
572
|
-
});
|
|
573
|
-
registerCtxGlobalCommand(ctx, {
|
|
574
|
-
stores,
|
|
575
|
-
...memoryRecall === undefined ? {} : { corpusDbPath: memoryRecall.dbPath },
|
|
576
|
-
// WP9: approving a candidate publishes it. Without the writer there is no
|
|
577
|
-
// way to make it visible, and the command says so rather than marking a
|
|
578
|
-
// candidate approved that nobody can see.
|
|
579
|
-
...memoryWriter === undefined || memoryWriter.status.ok !== true ? {} : { publisher: memoryWriter },
|
|
580
|
-
});
|
|
581
|
-
ctx.logger.info('magic-context dsh adapter: /ctx-profile and /ctx-global registered');
|
|
582
|
-
}
|
|
583
|
-
stores.setMeta('adapter_version', 'phase2');
|
|
584
|
-
stores.setMeta('memory_bridge', memoryRecall === undefined
|
|
585
|
-
? memorySettings === undefined ? 'off' : 'disabled-by-config'
|
|
586
|
-
: memoryRecall.status.ok
|
|
587
|
-
? `ok schema v${String(memoryRecall.status.schemaVersion)}`
|
|
588
|
-
: `unavailable: ${String(memoryRecall.status.detail)}`);
|
|
589
|
-
stores.setMeta('memory_write', describeMemoryWrite());
|
|
590
|
-
// ── Telemetry for the browser half (validation-period observability) ──────
|
|
591
|
-
// The Remote event whitelist is a compile-time constant, so a third-party
|
|
592
|
-
// bundle cannot subscribe to plugin events; a web route we own is the
|
|
593
|
-
// supported seam (verified by docs/dsh-plugin-ui-telemetry-contract.md).
|
|
594
|
-
//
|
|
595
|
-
// `ctx.get('webServer')` is a SNAPSHOT read: the web server's provider fiber
|
|
596
|
-
// is normally not active yet when a bundle applies, so reading it eagerly
|
|
597
|
-
// returned undefined and the route never registered (measured as a 404 in an
|
|
598
|
-
// isolated profile while the desktop instance happened to win the race).
|
|
599
|
-
// `ctx.inject` waits for the service and re-runs on its lifecycle, which is
|
|
600
|
-
// how @deepseek-ai/dsh-client-modules attaches its own route.
|
|
601
|
-
let telemetry;
|
|
602
|
-
ctx.inject(['webServer'], (scope) => {
|
|
603
|
-
const webServer = scope.webServer;
|
|
604
|
-
telemetry = createTelemetryService({
|
|
605
|
-
stores,
|
|
606
|
-
...journal === undefined ? {} : { journal },
|
|
607
|
-
meta: () => ({
|
|
608
|
-
engine: stores.getMeta('compaction_engine'),
|
|
609
|
-
memory: stores.getMeta('memory_bridge'),
|
|
610
|
-
memoryWrite: stores.getMeta('memory_write'),
|
|
611
|
-
toolsMode: toolMode,
|
|
612
|
-
}),
|
|
613
|
-
// Dreamer-slice KPIs for the panel: the viewed session's project
|
|
614
|
-
// verification coverage + the newest maintenance tally. Lazy getters —
|
|
615
|
-
// the panel polls them, nothing runs at mount.
|
|
616
|
-
memoryStats: (sessionId, identity) => {
|
|
617
|
-
if (memoryWriter === undefined)
|
|
618
|
-
return undefined;
|
|
619
|
-
// The identity telemetry resolved from this scope's newest recall comes
|
|
620
|
-
// first: it is the very project the header shows, and the process
|
|
621
|
-
// serves several sessions (first-seen would answer for someone else's
|
|
622
|
-
// project). Config and the session registry are the fallbacks.
|
|
623
|
-
const resolved = identity
|
|
624
|
-
?? config.memory?.project
|
|
625
|
-
?? (sessionId !== undefined ? projects.sessionIdentity.get(sessionId) : undefined)
|
|
626
|
-
?? [...projects.seenIdentities][0];
|
|
627
|
-
if (resolved === undefined)
|
|
628
|
-
return undefined;
|
|
629
|
-
try {
|
|
630
|
-
const stats = memoryWriter.stats(resolved);
|
|
631
|
-
return { total: stats.rows, verified: stats.verified, unverified: stats.unverified };
|
|
632
|
-
}
|
|
633
|
-
catch {
|
|
634
|
-
return undefined;
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
maintenanceLast: maintenance.maintenanceLast,
|
|
638
|
-
startedAt,
|
|
639
|
-
});
|
|
640
|
-
const service = telemetry;
|
|
641
|
-
scope.effect(() => webServer.register({
|
|
642
|
-
kind: 'prefix',
|
|
643
|
-
path: '/mc-telemetry',
|
|
644
|
-
handler: (req, res) => service.handle(req, res),
|
|
645
|
-
}));
|
|
646
|
-
journal?.info('boot', 'telemetry route attached for the browser half', {
|
|
647
|
-
telemetry: '/mc-telemetry',
|
|
648
|
-
routes: ['/mc-telemetry/summary', '/mc-telemetry/events'],
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
// ── Maintenance tick (the adapter's slice of MC's dreamer) ─────────────────
|
|
652
|
-
const maintenance = wireMaintenance({
|
|
653
|
-
ctx,
|
|
654
|
-
config,
|
|
655
|
-
stores,
|
|
656
|
-
journal,
|
|
657
|
-
llm,
|
|
658
|
-
writer: memoryWriter,
|
|
659
|
-
profileWriter,
|
|
660
|
-
vectorBackfill: lane.vectorBackfill,
|
|
661
|
-
routeHealth,
|
|
662
|
-
lastSessionRoute: () => lastSessionRoute,
|
|
663
|
-
projects,
|
|
664
|
-
disposers,
|
|
665
|
-
isDisposed: () => adapterDisposed,
|
|
666
|
-
});
|
|
667
|
-
// The occupancy gauge the native meter shows, from the SAME source: the
|
|
668
|
-
// host's `contextPressure` projection. projectedTokens is the last
|
|
669
|
-
// request's prompt-side usage plus the surface's signed movement since the
|
|
670
|
-
// sample — the answer for the NEXT request, not the last-request bill our
|
|
671
|
-
// pass ledger carries (the two read differently and the user noticed).
|
|
672
|
-
// A reading lands in the journal only when it is genuinely new (see
|
|
673
|
-
// dsh/context-pressure.ts): every distinct reading used to write a row,
|
|
674
|
-
// measured at ~10 rows/minute during heavy work. Telemetry serves the newest
|
|
675
|
-
// row to the panel, so both gauges agree within one threshold step.
|
|
676
|
-
ctx.inject(['sessionProjections'], (scope) => {
|
|
677
|
-
// Structural, not host-typed on purpose: `SessionProjectionMap` is
|
|
678
|
-
// merge-extensible and `contextPressure` is registered at runtime by the
|
|
679
|
-
// token meter, so the host type's key union cannot name our key.
|
|
680
|
-
const registry = scope.sessionProjections;
|
|
681
|
-
if (journal === undefined || registry === undefined)
|
|
682
|
-
return;
|
|
683
|
-
const lastBySession = new Map();
|
|
684
|
-
const disposeListener = registry.onChanged((session, key, value) => {
|
|
685
|
-
try {
|
|
686
|
-
if (key !== 'contextPressure')
|
|
687
|
-
return;
|
|
688
|
-
const reading = value;
|
|
689
|
-
const sid = typeof session?.id === 'string' ? session.id : '';
|
|
690
|
-
if (!shouldLogContextPressure(lastBySession.get(sid), reading))
|
|
691
|
-
return;
|
|
692
|
-
lastBySession.set(sid, { used: pressureUsedTokens(reading), window: reading.contextWindow ?? 0 });
|
|
693
|
-
if (lastBySession.size > 64) {
|
|
694
|
-
const oldest = lastBySession.keys().next().value;
|
|
695
|
-
if (oldest !== undefined)
|
|
696
|
-
lastBySession.delete(oldest);
|
|
697
|
-
}
|
|
698
|
-
const used = pressureUsedTokens(reading);
|
|
699
|
-
journal.info('context-pressure', `context pressure reading: ${String(used)} / ${String(reading.contextWindow ?? 0)} tok`, {
|
|
700
|
-
projectedTokens: reading.projectedTokens,
|
|
701
|
-
pressureTokens: reading.pressureTokens,
|
|
702
|
-
contextWindow: reading.contextWindow,
|
|
703
|
-
}, sid === '' ? undefined : sid);
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
// a failed reading must never break the host's projection drive
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
// The listener's disposer rides this inject fiber — a scope reload
|
|
710
|
-
// re-runs the whole inject and swaps listeners cleanly.
|
|
711
|
-
void disposeListener;
|
|
712
|
-
});
|
|
713
|
-
// Config-drift alarm (2026-10-12): the profile's `config` block is REPLACE
|
|
714
|
-
// semantics and once silently dropped three switch keys — the switches fell
|
|
715
|
-
// back to defaults with no error row, found ~12h later by inference. Diff
|
|
716
|
-
// this mount's effective snapshot against the previous mount's (persisted in
|
|
717
|
-
// the adapter meta table) and say so LOUDLY when something went off. First
|
|
718
|
-
// mount only seeds. Fail-open: a meta failure must not break the boot.
|
|
719
|
-
const switches = buildSwitchSnapshot(config);
|
|
720
|
-
try {
|
|
721
|
-
const previousRaw = stores.getMeta(SWITCH_SNAPSHOT_META_KEY);
|
|
722
|
-
if (previousRaw !== undefined) {
|
|
723
|
-
const previous = JSON.parse(previousRaw);
|
|
724
|
-
for (const row of diffSwitches(previous, switches)) {
|
|
725
|
-
const detail = { key: row.key, before: row.before, after: row.after };
|
|
726
|
-
if (row.severity === 'went-off') {
|
|
727
|
-
journal?.warn('config-drift', `switch went OFF: ${row.key} (${String(row.before)} -> ${String(row.after)})`, detail);
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
journal?.info('config-drift', `switch changed: ${row.key} (${String(row.before)} -> ${String(row.after)})`, detail);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
stores.setMeta(SWITCH_SNAPSHOT_META_KEY, JSON.stringify(switches));
|
|
735
|
-
}
|
|
736
|
-
catch (error) {
|
|
737
|
-
journal?.warn('config-drift', `switch snapshot diff failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
738
|
-
}
|
|
739
|
-
journal?.info('boot', `adapter booted (tools: ${toolMode}, historian: ${liveBoolean(config.historianEnabled, true) === false ? 'off' : llm === undefined ? 'no-llm' : 'on'})`, {
|
|
740
|
-
version: 'phase2',
|
|
741
|
-
dbPath: stores.dbPath,
|
|
742
|
-
schemaVersion: SCHEMA_VERSION,
|
|
743
|
-
recoveredHistorianRuns,
|
|
744
|
-
switches,
|
|
745
|
-
// Set only when this mount laid the own corpus down (fresh machine)
|
|
746
|
-
...lane.corpusBootstrap === undefined ? {} : { corpusBootstrap: lane.corpusBootstrap },
|
|
747
|
-
tools: { mode: toolMode, registered: config.registerTools !== false },
|
|
748
|
-
engine: stores.getMeta('compaction_engine'),
|
|
749
|
-
memory: stores.getMeta('memory_bridge'),
|
|
750
|
-
memoryWrite: stores.getMeta('memory_write'),
|
|
751
|
-
telemetry: telemetry === undefined ? 'pending (registers with webServer)' : '/mc-telemetry',
|
|
752
|
-
// Resolved once for display; the tick itself resolves at decision time.
|
|
753
|
-
maintenance: {
|
|
754
|
-
enabled: liveBoolean(config.maintenance?.enabled, true) !== false,
|
|
755
|
-
intervalMs: Math.max(60_000, liveNumber(config.maintenance?.intervalMs, 1_800_000)),
|
|
756
|
-
verify: llm === undefined
|
|
757
|
-
? 'off (no llm)'
|
|
758
|
-
: (() => {
|
|
759
|
-
const provider = liveString(config.maintenance?.verifyProvider, '');
|
|
760
|
-
const model = liveString(config.maintenance?.verifyModel, '');
|
|
761
|
-
if (provider.length > 0 && model.length > 0)
|
|
762
|
-
return `${provider}/${model}`;
|
|
763
|
-
// Unpinned: the tick follows the main conversation's latest route
|
|
764
|
-
// (last-seen-wins); the per-tick journal rows carry the actual route.
|
|
765
|
-
return 'follow-session';
|
|
766
|
-
})(),
|
|
767
|
-
},
|
|
768
|
-
historian: {
|
|
769
|
-
enabled: liveBoolean(config.historianEnabled, true) !== false && llm !== undefined,
|
|
770
|
-
// Resolved once for display; the lanes themselves re-read at use time.
|
|
771
|
-
route: historianRouteOf() === undefined
|
|
772
|
-
? 'follow-session'
|
|
773
|
-
: `${historianRouteOf().provider}/${historianRouteOf().model}`,
|
|
774
|
-
reasoningEffort: historianEffortOf() ?? 'default',
|
|
775
|
-
},
|
|
776
|
-
journal: { enabled: journal !== undefined, mirror: journal?.mirrorPath, retentionDays: liveNumber(config.journal?.retentionDays, 30) },
|
|
777
|
-
// 融合 v7 的待观察翻转(boot 行可见,巡检核对配置漂移)。
|
|
778
|
-
// compartmentGuardMode 已于 2026-10-16 退役,不再进 boot 行。
|
|
779
|
-
forceWaitReadyMs: liveNumber(config.policy?.forceWaitReadyMs, 0),
|
|
780
|
-
memoryScoringEnabled: liveBoolean(config.memory?.scoring?.enabled, true),
|
|
781
|
-
policy: {
|
|
782
|
-
executeThreshold: policy.executeThreshold,
|
|
783
|
-
forceThreshold: policy.forceThreshold,
|
|
784
|
-
assumedContextWindow: policy.assumedContextWindow,
|
|
785
|
-
ttlMs: policy.ttlMs,
|
|
786
|
-
protectedToolResults: policy.protectedToolResults,
|
|
787
|
-
historianProtectedNodes: policy.historianProtectedNodes,
|
|
788
|
-
historianMinNodes: policy.historianMinNodes,
|
|
789
|
-
historianTimeoutMs: policy.historianTimeoutMs,
|
|
790
|
-
historianMaxTokens: policy.historianMaxTokens,
|
|
791
|
-
historianMaxTokensCeiling: policy.historianMaxTokensCeiling,
|
|
792
|
-
historianMinBudgetTokens: policy.historianMinBudgetTokens,
|
|
793
|
-
baselineTokens: policy.baselineTokens,
|
|
794
|
-
memoryLimit: policy.memoryLimit,
|
|
795
|
-
},
|
|
796
|
-
});
|
|
797
|
-
ctx.effect(() => () => {
|
|
798
|
-
// Flag FIRST: a stale engine holding the old closure must see disposed
|
|
799
|
-
// before it can touch any of the handles being closed below.
|
|
800
|
-
adapterDisposed = true;
|
|
801
|
-
for (const dispose of disposers.splice(0))
|
|
802
|
-
dispose();
|
|
803
|
-
telemetry?.close();
|
|
804
|
-
memoryWriter?.close();
|
|
805
|
-
profileWriter?.close();
|
|
806
|
-
memorySearch?.close();
|
|
807
|
-
memoryRecall?.close();
|
|
808
|
-
stores.close();
|
|
809
|
-
}, 'magic-context dsh adapter listeners and stores');
|
|
810
|
-
ctx.logger.info('magic-context dsh adapter: phase-2 management loop wired');
|
|
811
|
-
}
|
|
812
|
-
// Public entry surface: only what external consumers (the POC suite and the
|
|
813
|
-
// loader's default export) actually import. Everything else is imported
|
|
814
|
-
// in-package from its owning module.
|
|
815
|
-
export { replaceToolResultWithPlaceholder } from "./dsh/seams.js";
|
|
816
|
-
export { reclaimForEmergency } from "./dsh/emergency.js";
|
|
817
|
-
export { openStores } from "./core/stores.js";
|
|
818
|
-
export { DEFAULT_POLICY } from "./core/policy.js";
|
|
819
|
-
export { openJournal, redactData, formatJournalLine, recoverOrphanHistorianRuns } from "./core/journal.js";
|
|
820
|
-
export { runManagementPass } from "./dsh/management-pass.js";
|
|
821
|
-
export { policyOf } from "./dsh/volatile.js";
|
|
822
|
-
/**
|
|
823
|
-
* The runtime schema the loader (and the plugin page's settings form) reads.
|
|
824
|
-
*
|
|
825
|
-
* Only the fields a user may tune are declared, and only those are `.volatile()`
|
|
826
|
-
* — the Host's `settings.describe` projects volatile fields exclusively, so an
|
|
827
|
-
* undeclared or non-volatile field simply has no form control. schemastery
|
|
828
|
-
* passes undeclared keys through untouched (verified), so `defineTool`,
|
|
829
|
-
* `compactionBase`, `historian` and the rest stay exactly as configured.
|
|
830
|
-
*
|
|
831
|
-
* Volatility is also the mechanism behind "apply immediately": the loader
|
|
832
|
-
* commits a volatile-only change into the live reference without remounting the
|
|
833
|
-
* fiber, and this plugin reads those references through {@link policyOf} at
|
|
834
|
-
* every decision point.
|
|
835
|
-
*
|
|
836
|
-
* `memory` is deliberately NOT declared here. schemastery materialises declared
|
|
837
|
-
* nested objects with their defaults, so declaring it would hand every profile
|
|
838
|
-
* a `memory` object even when the user configured none — and in this plugin the
|
|
839
|
-
* mere presence of `memory` opens the read/write bridge on the shared store.
|
|
840
|
-
* Memory stays opt-in by configuration; its thresholds are edited in the patch.
|
|
841
|
-
*/
|
|
842
|
-
export const Config = z.object({
|
|
843
|
-
historianEnabled: z.boolean().default(true).volatile(),
|
|
844
|
-
registerCompactionEngine: z.boolean().default(false).volatile(),
|
|
845
|
-
/** One notice entry in the session stream per action-bearing pass (see session-notice.ts). */
|
|
846
|
-
sessionNotices: z.boolean().default(true).volatile(),
|
|
847
|
-
/**
|
|
848
|
-
* Auto-search hint: one "you may recall something related" entry per user turn,
|
|
849
|
-
* built from the user's own words (see search-hint.ts).
|
|
850
|
-
*/
|
|
851
|
-
searchHint: z.boolean().default(true).volatile(),
|
|
852
|
-
/**
|
|
853
|
-
* Maintenance tick (the adapter's slice of MC's dreamer): TTL expiry always
|
|
854
|
-
* runs when the memory writer is writable; LLM verification additionally
|
|
855
|
-
* needs an explicit route because a tick has no session to inherit one from.
|
|
856
|
-
*/
|
|
857
|
-
maintenance: z.object({
|
|
858
|
-
enabled: z.boolean().default(true).volatile(),
|
|
859
|
-
intervalMs: z.number().min(60_000).default(1_800_000).volatile(),
|
|
860
|
-
/** Empty provider/model = verify off (schemastery objects cannot be optional). */
|
|
861
|
-
verifyProvider: z.string().default('').volatile(),
|
|
862
|
-
verifyModel: z.string().default('').volatile(),
|
|
863
|
-
}),
|
|
864
|
-
/**
|
|
865
|
-
* Generation route: the out-of-band historian and `/compact` summaries.
|
|
866
|
-
*
|
|
867
|
-
* A PAIR, like the maintenance route: both halves must be non-empty to pin;
|
|
868
|
-
* empty halves mean "follow the session's routed model", which is the
|
|
869
|
-
* shipped default (and the right one — the digest becomes future context, so
|
|
870
|
-
* the conversation's own model is usually the safe choice). Declaring the
|
|
871
|
-
* block is safe: unlike `memory`, its only effect is a route override, so a
|
|
872
|
-
* materialised empty object switches nothing on.
|
|
873
|
-
*/
|
|
874
|
-
historian: z.object({
|
|
875
|
-
provider: z.string().default('').volatile(),
|
|
876
|
-
model: z.string().default('').volatile(),
|
|
877
|
-
/** Optional provider/model-specific reasoning effort; blank = host default. */
|
|
878
|
-
reasoningEffort: z.string().default('').volatile(),
|
|
879
|
-
}),
|
|
880
|
-
journal: z.object({
|
|
881
|
-
enabled: z.boolean().default(true).volatile(),
|
|
882
|
-
level: z.string().default('info').volatile(),
|
|
883
|
-
/** min(1): a stray 0/negative would prune the whole table at open. */
|
|
884
|
-
retentionDays: z.number().min(1).default(30).volatile(),
|
|
885
|
-
mirror: z.boolean().default(true).volatile(),
|
|
886
|
-
}),
|
|
887
|
-
policy: z.object({
|
|
888
|
-
/** Usage percent (0-100) at which the execute band opens. */
|
|
889
|
-
executeThreshold: z.number().min(0).max(100).default(65).volatile(),
|
|
890
|
-
/** Usage percent (0-100) at which reclaim becomes unconditional. */
|
|
891
|
-
forceThreshold: z.number().min(0).max(100).default(85).volatile(),
|
|
892
|
-
/**
|
|
893
|
-
* Window assumed for scheduling when the routed model discloses none.
|
|
894
|
-
* Without it a route that publishes no window defers 'no-window' forever,
|
|
895
|
-
* which silently disables the execute band.
|
|
896
|
-
*/
|
|
897
|
-
assumedContextWindow: z.number().min(1).default(256_000).volatile(),
|
|
898
|
-
ttlMs: z.number().default(300_000).volatile(),
|
|
899
|
-
protectedToolResults: z.number().default(8).volatile(),
|
|
900
|
-
historianProtectedNodes: z.number().default(8).volatile(),
|
|
901
|
-
historianMinNodes: z.number().default(3).volatile(),
|
|
902
|
-
/** Wall-clock budget for one historian run; a spent budget journals `failed`.
|
|
903
|
-
* 120s: large backlogs (600+ pending nodes) measured ~60s+ per digest. */
|
|
904
|
-
historianTimeoutMs: z.number().default(120_000).volatile(),
|
|
905
|
-
/** Generation cap for the historian; the historian raises anything below 8192. */
|
|
906
|
-
historianMaxTokens: z.number().default(8_192).volatile(),
|
|
907
|
-
/** Ceiling the historian may escalate to after a token-cap truncation (retry doubling). */
|
|
908
|
-
historianMaxTokensCeiling: z.number().default(32_768).volatile(),
|
|
909
|
-
historianMinBudgetTokens: z.number().default(5_000).volatile(),
|
|
910
|
-
baselineTokens: z.number().default(2_000).volatile(),
|
|
911
|
-
memoryBudgetTokens: z.number().default(8_000).volatile(),
|
|
912
|
-
memoryLimit: z.number().default(40).volatile(),
|
|
913
|
-
/**
|
|
914
|
-
* Overflow-path wait on an in-flight historian before the drop lane
|
|
915
|
-
* (0 = never wait — the shipped default; flip after observation).
|
|
916
|
-
*/
|
|
917
|
-
forceWaitReadyMs: z.number().min(0).default(0).volatile(),
|
|
918
|
-
}),
|
|
919
|
-
});
|
|
920
|
-
/**
|
|
921
|
-
* The plugin as a loadable object. This is what every loading mode mounts:
|
|
922
|
-
* the profile loader takes a package's default export, and in-tree glue and
|
|
923
|
-
* tests pass it to `ctx.plugin` directly — one source of truth for the
|
|
924
|
-
* required services (`inject`), the apply entry, and the runtime `Config`.
|
|
925
|
-
*
|
|
926
|
-
* `Config` MUST live on the default export: the loader's `unwrapExports` picks
|
|
927
|
-
* the default object and reads `Config` from it, so a named `export const
|
|
928
|
-
* Config` alone is silently ignored and the configuration never parses
|
|
929
|
-
* (measured on cordis 4.0.4).
|
|
930
|
-
*/
|
|
931
|
-
export default { name, inject, apply, Config };
|