prism-mcp-server 20.22.0 → 20.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,33 @@ 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.2
239
+
240
+ **Why it matters:** an agent that followed `session_task_route`'s advice was
241
+ refused on explicit oMLX before anything ran.
242
+
243
+ - **A complexity hint no longer refuses explicit oMLX.** `session_task_route`
244
+ recommends a `task_complexity` for every code task. That hint picks Ollama's
245
+ starting tier, but on explicit oMLX it lowered the model ceiling, so a call
246
+ that passed the recommended arguments was refused with `omlx_model_ceiling`.
247
+ Now only an explicit `model_ceiling` below the top tier, or a plan below it,
248
+ refuses. Thinking on oMLX stays with the server's settings.
249
+
250
+ ## What's New in v20.22.1
251
+
252
+ **Why it matters:** on explicit oMLX, a long prompt could keep the device busy
253
+ for minutes while being classified, and `timeout_ms` did not limit it.
254
+
255
+ - **The call budget now covers classification.** `timeout_ms` (default 300 s)
256
+ starts before the safety screen, and each classifier read is bounded by the
257
+ remaining budget as well as its own 90 s.
258
+ - **A failed read ends the classification.** An oversize prompt is read in
259
+ 3,600-character windows; a read that fails already means refusal on this
260
+ path, so the remaining windows are no longer read. Before, a stalled server
261
+ could cost about four minutes per window.
262
+ - **Tests:** the owned-job marker tests pin the size limit and the
263
+ non-blocking open directly.
264
+
235
265
  ## What's New in v20.22.0
236
266
 
237
267
  **Why it matters:** on Apple Silicon, `prism_infer` can now draft code on an
@@ -2206,12 +2206,14 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2206
2206
  // No trusted binding maps an oMLX model to a logical tier, and the
2207
2207
  // configured Flash model outweighs every Ollama tier. Below the top
2208
2208
  // ceiling, from the caller or the plan, refuse before any network,
2209
- // classification or residency change.
2210
- if (effectiveCeiling !== "27b") {
2209
+ // classification or residency change. A task_complexity hint only picks
2210
+ // Ollama's starting tier (session_task_route recommends one for every
2211
+ // code task), so it is not a ceiling here; an explicit model_ceiling is.
2212
+ if (clampCeiling(args.model_ceiling ?? "27b", ent.model_ceiling) !== "27b") {
2211
2213
  attempts.push({ tier: "omlx", reason: "omlx_model_ceiling" });
2212
2214
  if (wantReport)
2213
2215
  return refusedResult("omlx_model_ceiling");
2214
- throw new Error("prism_infer: omlx_model_ceiling; explicit oMLX needs the top model ceiling");
2216
+ throw new Error("prism_infer: omlx_model_ceiling; explicit oMLX needs the top model ceiling from model_ceiling and the plan");
2215
2217
  }
2216
2218
  if (!Number.isInteger(localMaxTokens) || localMaxTokens <= 0
2217
2219
  || !Number.isFinite(temperature) || temperature < 0
@@ -2240,6 +2242,9 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2240
2242
  const loaded = await deps.listLoaded();
2241
2243
  if (omlxTarget && installed === null)
2242
2244
  throw new Error("prism_infer: omlx_classifier_unavailable");
2245
+ // Explicit oMLX: timeout_ms bounds the whole call, the Ollama unload and
2246
+ // classification included.
2247
+ const omlxDeadline = Date.now() + (args.timeout_ms ?? 300_000);
2243
2248
  // Ollama is unloaded during an explicit oMLX call (owner, 2026-10-15; this
2244
2249
  // replaces plan section 4's "never evict another Ollama user's weights").
2245
2250
  // Under the device lease, before classification: unload every resident
@@ -2328,7 +2333,7 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2328
2333
  const baseL1 = deps.callLayer1 ?? defaultCallLayer1;
2329
2334
  const generationTarget = omlxTarget, runtime = omlxRuntime;
2330
2335
  const omlxL1Fetch = generationTarget && runtime
2331
- ? omlxLayer1Fetch(async (request) => guardedOmlxGenerate(runtime, omlxModelTarget(generationTarget, OMLX_LAYER1_MODEL), request, deps.freemem))
2336
+ ? omlxLayer1Fetch(async (request) => guardedOmlxGenerate(runtime, omlxModelTarget(generationTarget, OMLX_LAYER1_MODEL), request, deps.freemem), () => omlxDeadline - Date.now())
2332
2337
  : undefined;
2333
2338
  const l1fn = omlxL1Fetch
2334
2339
  ? (...[prompt, url, model, , images, opts]) => baseL1(prompt, url, model, omlxL1Fetch, images, opts)
@@ -2720,7 +2725,6 @@ async function runInferAfterInputSafety(args, deps, t0, temperature) {
2720
2725
  // Explicit oMLX classified on oMLX, so nothing of this call's is in Ollama.
2721
2726
  // Anything resident now belongs to another user: guardedOmlxGenerate
2722
2727
  // refuses generation (omlx_memory_contention) before any request.
2723
- const omlxDeadline = Date.now() + (args.timeout_ms ?? 300_000);
2724
2728
  const callLocal = omlxTarget && omlxRuntime
2725
2729
  ? async (_url, _model, prompt, system, maxTokens, temperature, timeoutMs, think, _images, history) => {
2726
2730
  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.2",
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",