prism-mcp-server 20.22.0 → 20.22.1

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 CHANGED
@@ -99,8 +99,11 @@ Layer 1's rules only refuse here; the classifier reads everything else, and a
99
99
  prompt over 4,000 characters is read whole in 3,600-character windows (the most
100
100
  severe verdict stands), never as an excerpt. A classifier failure, an unreadable
101
101
  answer, or a missing classifier refuses the call; there is no keyword or Ollama
102
- fallback. A classifier read may take up to 90 seconds, so a classifier that
103
- macOS paged out during a long idle is waited for rather than refused.
102
+ fallback. Each classifier read is bounded by 90 seconds and by the call's
103
+ remaining `timeout_ms`, so a classifier that macOS paged out during a long idle
104
+ is waited for rather than refused, and an oversize prompt read in windows
105
+ cannot outlast the call budget; the first unrecoverable read ends the
106
+ classification.
104
107
 
105
108
  Configured oMLX sampling and reasoning are preserved unless `temperature` or
106
109
  `think` is explicit, including through repairs. Generic thinking-disable retries
@@ -232,6 +235,21 @@ or by re-enabling after each run.
232
235
  <details>
233
236
  <summary>Release history (optional)</summary>
234
237
 
238
+ ## What's New in v20.22.1
239
+
240
+ **Why it matters:** on explicit oMLX, a long prompt could keep the device busy
241
+ for minutes while being classified, and `timeout_ms` did not limit it.
242
+
243
+ - **The call budget now covers classification.** `timeout_ms` (default 300 s)
244
+ starts before the safety screen, and each classifier read is bounded by the
245
+ remaining budget as well as its own 90 s.
246
+ - **A failed read ends the classification.** An oversize prompt is read in
247
+ 3,600-character windows; a read that fails already means refusal on this
248
+ path, so the remaining windows are no longer read. Before, a stalled server
249
+ could cost about four minutes per window.
250
+ - **Tests:** the owned-job marker tests pin the size limit and the
251
+ non-blocking open directly.
252
+
235
253
  ## What's New in v20.22.0
236
254
 
237
255
  **Why it matters:** on Apple Silicon, `prism_infer` can now draft code on an
@@ -2240,6 +2240,9 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2240
2240
  const loaded = await deps.listLoaded();
2241
2241
  if (omlxTarget && installed === null)
2242
2242
  throw new Error("prism_infer: omlx_classifier_unavailable");
2243
+ // Explicit oMLX: timeout_ms bounds the whole call, the Ollama unload and
2244
+ // classification included.
2245
+ const omlxDeadline = Date.now() + (args.timeout_ms ?? 300_000);
2243
2246
  // Ollama is unloaded during an explicit oMLX call (owner, 2026-10-15; this
2244
2247
  // replaces plan section 4's "never evict another Ollama user's weights").
2245
2248
  // Under the device lease, before classification: unload every resident
@@ -2328,7 +2331,7 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2328
2331
  const baseL1 = deps.callLayer1 ?? defaultCallLayer1;
2329
2332
  const generationTarget = omlxTarget, runtime = omlxRuntime;
2330
2333
  const omlxL1Fetch = generationTarget && runtime
2331
- ? omlxLayer1Fetch(async (request) => guardedOmlxGenerate(runtime, omlxModelTarget(generationTarget, OMLX_LAYER1_MODEL), request, deps.freemem))
2334
+ ? omlxLayer1Fetch(async (request) => guardedOmlxGenerate(runtime, omlxModelTarget(generationTarget, OMLX_LAYER1_MODEL), request, deps.freemem), () => omlxDeadline - Date.now())
2332
2335
  : undefined;
2333
2336
  const l1fn = omlxL1Fetch
2334
2337
  ? (...[prompt, url, model, , images, opts]) => baseL1(prompt, url, model, omlxL1Fetch, images, opts)
@@ -2720,7 +2723,6 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2720
2723
  // Explicit oMLX classified on oMLX, so nothing of this call's is in Ollama.
2721
2724
  // Anything resident now belongs to another user: guardedOmlxGenerate
2722
2725
  // refuses generation (omlx_memory_contention) before any request.
2723
- const omlxDeadline = Date.now() + (args.timeout_ms ?? 300_000);
2724
2726
  const callLocal = omlxTarget && omlxRuntime
2725
2727
  ? async (_url, _model, prompt, system, maxTokens, temperature, timeoutMs, think, _images, history) => {
2726
2728
  const messages = [...(system === undefined ? [] : [{ role: "system", content: system }]),
@@ -550,7 +550,9 @@ images, opts) {
550
550
  const verdict = await callLayer1Unleased(window, ollamaUrl, model, fetchImpl, images, opts);
551
551
  if (VERDICT_SEVERITY[verdict] > VERDICT_SEVERITY[worst])
552
552
  worst = verdict;
553
- if (worst === "OBVIOUS_RESERVED")
553
+ // Reserved decides the call; under strict errors an ERROR does too (the
554
+ // call refuses), so the remaining windows are never read.
555
+ if (worst === "OBVIOUS_RESERVED" || (opts?.strictErrors && verdict === "ERROR"))
554
556
  break;
555
557
  }
556
558
  return worst;
@@ -29,8 +29,13 @@ function chatMessages(value) {
29
29
  * deadline.
30
30
  * - Every request asks the provider not to keep its prompt cache: a classifier
31
31
  * request is unique and never reused. */
32
- export function omlxLayer1Fetch(generate) {
32
+ export function omlxLayer1Fetch(generate, remainingMs = () => OMLX_LAYER1_TIMEOUT_MS) {
33
33
  return (async (_input, init) => {
34
+ // Each read is bounded by the call's remaining timeout_ms as well, so an
35
+ // oversize prompt read in windows cannot outlast the budget the caller set.
36
+ const timeoutMs = Math.min(OMLX_LAYER1_TIMEOUT_MS, Math.floor(remainingMs()));
37
+ if (timeoutMs <= 0)
38
+ return Response.json({ error: "timeout" }, { status: 503 });
34
39
  let body;
35
40
  try {
36
41
  body = JSON.parse(String(init?.body));
@@ -46,7 +51,7 @@ export function omlxLayer1Fetch(generate) {
46
51
  return new Response(null, { status: 400 });
47
52
  const temperature = body.options?.temperature;
48
53
  const result = await generate({ messages, maxTokens, temperature: typeof temperature === "number" ? temperature : 0,
49
- timeoutMs: OMLX_LAYER1_TIMEOUT_MS, think: body.think === true, cacheStore: false });
54
+ timeoutMs, think: body.think === true, cacheStore: false });
50
55
  return result.ok
51
56
  ? Response.json({ done: true, done_reason: result.doneReason, message: { role: "assistant", content: result.text } })
52
57
  : Response.json({ error: result.reason }, { status: 503 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.22.0",
3
+ "version": "20.22.1",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
5
  "description": "Persistent session memory for AI coding agents that never leaves your machine — including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
6
6
  "module": "index.ts",