prism-mcp-server 20.21.14 → 20.21.16
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 +138 -77
- package/dist/config.js +15 -0
- package/dist/dashboard/accountRouter.js +5 -0
- package/dist/dashboard/ui.js +17 -8
- package/dist/server.js +8 -2
- package/dist/storage/inferMetricsLedger.js +40 -3
- package/dist/tools/definitions.js +36 -1
- package/dist/tools/handlers.js +29 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/ledgerHandlers.js +12 -3
- package/dist/tools/prismInferHandler.js +726 -19
- package/dist/tools/savingsHandler.js +40 -0
- package/dist/utils/answerGrounding.js +130 -0
- package/dist/utils/entitlements.js +10 -6
- package/dist/utils/inferenceMetrics.js +1 -0
- package/dist/utils/inferencePolicy.js +374 -0
- package/dist/utils/layer1.js +41 -7
- package/dist/utils/ollamaSystemPrompt.js +93 -0
- package/dist/utils/pseudonymize.js +207 -0
- package/dist/utils/qualityGate.js +12 -4
- package/dist/utils/routeContract.js +23 -0
- package/dist/utils/youcomApi.js +189 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,6 +158,42 @@ or by re-enabling after each run.
|
|
|
158
158
|
<details>
|
|
159
159
|
<summary>Release history (optional)</summary>
|
|
160
160
|
|
|
161
|
+
## What's New in v20.21.16
|
|
162
|
+
|
|
163
|
+
### Conversations: checked on your device, and free with an account
|
|
164
|
+
|
|
165
|
+
- A local answer to a conversation is checked on your device before it is
|
|
166
|
+
served. If the check fails or cannot run, the answer goes to the cloud when
|
|
167
|
+
your plan and settings allow it, and is withheld otherwise.
|
|
168
|
+
- On Pro and Team, a local answer that passes is also confirmed by Synalux, on
|
|
169
|
+
a copy pseudonymized on your device; the mapping stays on your device. With
|
|
170
|
+
an image attached, or with the cloud off, the checked local answer is served.
|
|
171
|
+
- When the on-device screen's 4b is unsure about a conversation, the 9b reads
|
|
172
|
+
it again before the conversation is refused or sent to the cloud.
|
|
173
|
+
- Everything local is free with no account and no cap on the local model size.
|
|
174
|
+
A free Synalux account adds multi-turn `prism_infer` and 20 cloud answers a
|
|
175
|
+
day. Pro ($15/month) and Team ($20/seat/month) are Prism's own plans.
|
|
176
|
+
- Optional You.com web search: set `YDC_API_KEY` to enable
|
|
177
|
+
`youcom_web_search` (contributed by @mouse-value-add in #227).
|
|
178
|
+
- Fixed: the 2b and 4b no longer answer Prism's own requests as a tool router.
|
|
179
|
+
A system prompt built into those models had turned some answers into tool
|
|
180
|
+
calls and made the on-device screen refuse some ordinary requests.
|
|
181
|
+
|
|
182
|
+
## What's New in v20.21.15
|
|
183
|
+
|
|
184
|
+
### Follow-ups: see what runs locally, and a stricter screen when the classifier is down
|
|
185
|
+
|
|
186
|
+
- `local_savings` (and `prism savings`) shows a line for follow-ups, calls that
|
|
187
|
+
carried your conversation. It covers how many the local model answered (and
|
|
188
|
+
how many of those the 9b answered), how many the on-device screen refused and
|
|
189
|
+
at which stage, how many your plan's multi-turn limits refused, and how many
|
|
190
|
+
went to the cloud.
|
|
191
|
+
- A short chat answer ("16", "Yes") counts as an answer. Before, the quality
|
|
192
|
+
gate treated it as empty, and a paid plan re-asked the cloud.
|
|
193
|
+
- When the on-device classifier fails every read on a follow-up, the follow-up
|
|
194
|
+
is no longer answered on a keyword check alone. It goes to the cloud if your
|
|
195
|
+
plan allows it, and is refused otherwise.
|
|
196
|
+
|
|
161
197
|
## What's New in v20.21.14
|
|
162
198
|
|
|
163
199
|
### Skills load when they help, and you can see why they loaded
|
|
@@ -1135,7 +1171,7 @@ The free tier runs entirely on your machine. Paid tiers add cloud sync through t
|
|
|
1135
1171
|
| Inference | Local Ollama models | Local models + Gemini 3.6 Flash fallback |
|
|
1136
1172
|
| API keys required | None | Synalux subscription key |
|
|
1137
1173
|
| Web search / scrape | Not included | Via Synalux portal (provider keys server-side) |
|
|
1138
|
-
| What leaves your machine | Nothing | Memory text, file paths, search queries, and inference prompts/drafts when their cloud feature is used, sent to the portal over TLS. Cloud memory writes
|
|
1174
|
+
| What leaves your machine | Nothing | Memory text, file paths, search queries, and inference prompts/drafts when their cloud feature is used, sent to the portal over TLS. Cloud memory writes have personal identifiers redacted; inference and route requests are transient. |
|
|
1139
1175
|
| Works offline | ✅ | Local features yes; sync/cloud no |
|
|
1140
1176
|
|
|
1141
1177
|
**Handling sensitive data.** Cloud memory writes pass through automatic
|
|
@@ -1144,8 +1180,7 @@ and clinical identifiers are stripped before storage). Cloud inference and
|
|
|
1144
1180
|
route correction send the request over TLS for processing and do not store it
|
|
1145
1181
|
as Prism memory. The **local tier** (no Synalux key) is the air-gap;
|
|
1146
1182
|
`route_guard: "local"` only skips the route correction, and `cloud_fallback`
|
|
1147
|
-
and `verify` govern the other two channels.
|
|
1148
|
-
Business Associate Agreement.
|
|
1183
|
+
and `verify` govern the other two channels.
|
|
1149
1184
|
|
|
1150
1185
|
---
|
|
1151
1186
|
|
|
@@ -1171,8 +1206,8 @@ and telemetry still contact the portal and carry neither.
|
|
|
1171
1206
|
|---|---|---|---|---|---|---|
|
|
1172
1207
|
| Qwen3.5-4B Q4_K_S | `prism-coder:2b` | 3.3 GB | ✅ | 100% | On-device / lowest RAM (4.5 GiB free) | Free |
|
|
1173
1208
|
| Qwen3.5-4B Q4_K_M | `prism-coder:4b` | 3.5 GB | ✅ | 100% | Verifier (5.2 GiB free) | Free |
|
|
1174
|
-
| Qwen3.5-9B (LoRA) | `prism-coder:9b` | 6.7 GB | ✅ | 95.7%² | Default router / workhorse (9 GiB free) |
|
|
1175
|
-
| Qwen3.5-27B (LoRA) | `prism-coder:27b` | 16.8 GB | — | 100% | Complex code / quality (21 GiB free) |
|
|
1209
|
+
| Qwen3.5-9B (LoRA) | `prism-coder:9b` | 6.7 GB | ✅ | 95.7%² | Default router / workhorse (9 GiB free) | Free |
|
|
1210
|
+
| Qwen3.5-27B (LoRA) | `prism-coder:27b` | 16.8 GB | — | 100% | Complex code / quality (21 GiB free) | Free |
|
|
1176
1211
|
|
|
1177
1212
|
¹ Self-run on a narrow 115-case MCP tool-selection suite, `temperature: 0`,
|
|
1178
1213
|
measured through the call path `prism_infer` actually uses (`/api/chat`, each
|
|
@@ -1284,74 +1319,64 @@ an image is never escalated; screenshots stay on this device.
|
|
|
1284
1319
|
|
|
1285
1320
|
## Why Prism Coder
|
|
1286
1321
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
that another product lacks an unlisted feature.
|
|
1292
|
-
|
|
1293
|
-
Legend: ✅ documented, ◐ conditional or plan-dependent, — not compared, ? verify
|
|
1294
|
-
with the provider.
|
|
1295
|
-
|
|
1296
|
-
| Capability | Prism Coder | GitHub Copilot | Cursor | Amazon Q Developer |
|
|
1297
|
-
|---|:---:|:---:|:---:|:---:|
|
|
1298
|
-
| Local/open-weight inference | ✅ | ◐ | ◐ | ◐ |
|
|
1299
|
-
| Offline workflow | ✅ | ◐ | ? | ? |
|
|
1300
|
-
| Cross-session memory | ✅ | ◐ ([docs](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference)) | ◐ | ◐ |
|
|
1301
|
-
| MCP integration | ✅ | ✅ ([docs](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference)) | ✅ ([pricing](https://cursor.com/en-US/pricing)) | ◐ |
|
|
1302
|
-
| Local-first model routing | ✅ | ◐ | ◐ | ◐ |
|
|
1303
|
-
| Session drift and grounding checks | ✅ | — | — | — |
|
|
1304
|
-
| Setup surface | ✅ five hosts | ✅ CLI/IDE | ✅ editor/agents | ✅ IDE/CLI ([overview](https://aws.amazon.com/q/developer/build/)) |
|
|
1305
|
-
| Pricing model | ✅ Synalux tiers | ◐ | ◐ ([pricing](https://cursor.com/en-US/pricing)) | ✅ free + $19 Pro ([pricing](https://aws.amazon.com/q/developer/pricing/)) |
|
|
1322
|
+
**Your local data stays on your device.** Memory, local models and the answer
|
|
1323
|
+
check run on your machine, with no account. Data leaves it only when you turn
|
|
1324
|
+
on a cloud feature, and then only what that feature needs; see
|
|
1325
|
+
[Local-first and privacy](#local-first-and-privacy).
|
|
1306
1326
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1327
|
+
- **Free without an account**, with no cap on the local model size.
|
|
1328
|
+
- **Memory and models in one MCP server:** persistent memory, a knowledge graph
|
|
1329
|
+
and automatic routing to local Ollama models, with cloud answers only when
|
|
1330
|
+
your plan and settings allow them.
|
|
1331
|
+
- **Checked answers:** a multi-turn local answer is checked on your device
|
|
1332
|
+
before it is served.
|
|
1310
1333
|
|
|
1311
|
-
###
|
|
1334
|
+
### How it compares
|
|
1312
1335
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
| Cloud fallback | ✅ optional | — | ◐ provider-dependent | ◐ | ◐ |
|
|
1317
|
-
| Persistent memory | ✅ | — | ◐ project context | ✅ | ✅ |
|
|
1318
|
-
| Knowledge/tool integration | ✅ MCP + ingestion | ◐ APIs | ◐ integrations | ✅ SDK/API | ✅ SDK/API |
|
|
1319
|
-
| MCP server | ✅ native | — | ◐ client integration | ◐ client integration | ◐ client integration |
|
|
1336
|
+
Prices from each vendor's pricing page, checked on 2026-09-26. Plans change
|
|
1337
|
+
often; follow the links for current terms. The table compares published plans
|
|
1338
|
+
only; it is not a claim that a product lacks an unlisted feature.
|
|
1320
1339
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
Prism
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1340
|
+
| Product | What it is | Free | Individual | Team, per seat |
|
|
1341
|
+
|---|---|---|---|---|
|
|
1342
|
+
| **Prism Coder** | Local memory and model routing (MCP), optional cloud | Everything local, no account | Pro $15/mo | $20, 2-seat minimum |
|
|
1343
|
+
| [GitHub Copilot](https://docs.github.com/en/copilot/get-started/plans) | Coding assistant | Limited | Pro $10, Pro+ $39, Max $100 | Business $19, Enterprise $39 |
|
|
1344
|
+
| [Cursor](https://cursor.com/pricing) | AI code editor | Hobby, limited | Pro $20 | Teams $40 |
|
|
1345
|
+
| [Windsurf](https://devin.ai/blog/windsurf-pricing-plans) | AI code editor | Free plan | Pro $20, Max $200 | Teams $40 |
|
|
1346
|
+
| [Claude](https://claude.com/pricing) | Assistant; Claude Code from Pro | Free (no Claude Code) | Pro $20, Max from $100 | Team $25 ($20 billed annually) |
|
|
1347
|
+
| [Amazon Q Developer](https://aws.amazon.com/q/developer/pricing/) | Coding assistant | 50 agentic requests a month | Pro $19 per user | -- |
|
|
1348
|
+
| [Ollama](https://ollama.com/pricing) | Local model runtime, cloud models | Local models, starter cloud credits | Pro $20 ($60 of cloud credits), Max $100 | Team $500/mo, unlimited users |
|
|
1349
|
+
| [Mem0](https://mem0.ai/pricing) | Memory API | 10,000 adds, 1,000 retrievals a month | Starter $19; graph memory from Pro $249 | -- |
|
|
1350
|
+
| [Supermemory](https://supermemory.ai/pricing/) | Memory API | $5 of credits a month | Pro $19 (3 seats), Max $100 | -- |
|
|
1351
|
+
| [Zep](https://www.getzep.com/pricing/) | Memory (knowledge graph) | 10,000 credits a month | Flex $125 | -- |
|
|
1352
|
+
| [Pieces](https://pieces.app/pricing) | On-device developer memory | None (7-day trial) | Pro $18.99 | Enterprise $22.99 |
|
|
1353
|
+
| [MemPalace](https://www.mempalace.net/) | Open-source local memory (MIT) | Free | -- | -- |
|
|
1327
1354
|
|
|
1328
1355
|
---
|
|
1329
1356
|
|
|
1330
1357
|
## Plans
|
|
1331
1358
|
|
|
1332
|
-
|
|
1359
|
+
Everything that runs on your machine is free, with no account and no cap on the local model. A free Synalux account adds what needs Synalux; Pro and Team add cloud features and higher limits.
|
|
1333
1360
|
|
|
1334
|
-
| | **Free** | **
|
|
1361
|
+
| | **Free** (no account) | **Free account** | **Pro** $15/mo | **Team** $20/seat/mo |
|
|
1335
1362
|
|---|---|---|---|---|
|
|
1336
|
-
|
|
|
1337
|
-
|
|
|
1338
|
-
|
|
|
1339
|
-
| Cloud
|
|
1340
|
-
| Cloud
|
|
1341
|
-
|
|
|
1342
|
-
| Cloud
|
|
1343
|
-
|
|
|
1344
|
-
|
|
|
1345
|
-
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
| HIPAA BAA | -- | -- | -- | ✅ |
|
|
1349
|
-
|
|
1350
|
-
Paid plans start with a 14-day trial. No card is required to start; add payment
|
|
1363
|
+
| Local models via Ollama (automatic `prism_infer` routing) | up to 27b | up to 27b | up to 27b | up to 27b |
|
|
1364
|
+
| Local memory, knowledge graph, dashboard, MCP tools | ✅ | ✅ | ✅ | ✅ |
|
|
1365
|
+
| Multi-turn `prism_infer`, with the on-device answer check | -- | 12 turns / 32k chars | 12 turns / 32k chars | 20 turns / 64k chars |
|
|
1366
|
+
| Cloud answers (Gemini 3.6 Flash; a free account's conversations: 3.5 Flash-Lite) | -- | 20/day | 1,000/month (500 in the trial; 200/day max) | 1,500/month per seat (200/day max) |
|
|
1367
|
+
| Cloud confirmation of a local answer (on a pseudonymized copy) | -- | -- | ✅ | ✅ |
|
|
1368
|
+
| Grounding verifier (fact-check AI output; counts as a cloud answer) | -- | -- | ✅ | ✅ |
|
|
1369
|
+
| Cloud search | -- | -- | 1,000/month (150/day max) | 1,500/month per seat (150/day max) |
|
|
1370
|
+
| Memory sync (cloud) | -- | -- | ✅ | ✅ |
|
|
1371
|
+
| Full skill library, super-skills, agent routing, account and team skills | -- | -- | ✅ | ✅ |
|
|
1372
|
+
| Seats | 1 | 1 | 1 | 2 or more |
|
|
1373
|
+
|
|
1374
|
+
Pro starts with a 14-day trial. No card is required to start; add payment
|
|
1351
1375
|
details before the trial ends to continue, or the subscription cancels
|
|
1352
|
-
automatically and
|
|
1353
|
-
|
|
1354
|
-
|
|
1376
|
+
automatically and Prism Free remains available. View the exact deadline under
|
|
1377
|
+
**⚙ Account & Settings → Account** in `prism dashboard`. A cloud confirmation
|
|
1378
|
+
counts as one of the day's cloud answers. To start a Team plan:
|
|
1379
|
+
[contact sales](https://synalux.ai/support).
|
|
1355
1380
|
|
|
1356
1381
|
---
|
|
1357
1382
|
|
|
@@ -1445,11 +1470,11 @@ It is paid because it cannot run without Synalux behind it:
|
|
|
1445
1470
|
```typescript
|
|
1446
1471
|
// Call 1
|
|
1447
1472
|
prism_infer({ prompt: "My project codename is Nightjar. Reply OK.", mode: "chat" })
|
|
1448
|
-
// → "OK" (local
|
|
1473
|
+
// → "OK" (local model, $0)
|
|
1449
1474
|
|
|
1450
1475
|
// Call 2 — the model never saw call 1
|
|
1451
1476
|
prism_infer({ prompt: "What is my codename? One word.", mode: "chat" })
|
|
1452
|
-
// → "I don't have that information." (local
|
|
1477
|
+
// → "I don't have that information." (local model, correct and useless)
|
|
1453
1478
|
|
|
1454
1479
|
// Call 3 — a coding follow-up with no thread
|
|
1455
1480
|
prism_infer({ prompt: "Now add a timeout parameter to it.", mode: "code" })
|
|
@@ -1474,7 +1499,7 @@ prism_infer({
|
|
|
1474
1499
|
prompt: "What is my codename? One word.",
|
|
1475
1500
|
mode: "chat",
|
|
1476
1501
|
})
|
|
1477
|
-
// → "Nightjar" (local
|
|
1502
|
+
// → "Nightjar" (local model, $0; history_turns: 2)
|
|
1478
1503
|
|
|
1479
1504
|
prism_infer({
|
|
1480
1505
|
messages: [
|
|
@@ -1484,7 +1509,7 @@ prism_infer({
|
|
|
1484
1509
|
prompt: "Write the one-line call that stores its result in n.",
|
|
1485
1510
|
mode: "code",
|
|
1486
1511
|
})
|
|
1487
|
-
// → "n = countActiveUsers(data)" (local
|
|
1512
|
+
// → "n = countActiveUsers(data)" (local model, $0)
|
|
1488
1513
|
|
|
1489
1514
|
// A turn the on-device screen finds uncertain, alone or in context, is not
|
|
1490
1515
|
// served locally: it goes to Synalux cloud on a paid plan, or is refused with
|
|
@@ -1501,9 +1526,14 @@ prism_infer({
|
|
|
1501
1526
|
// → "SYN-4471" (Gemini 3.6 Flash; used_cloud: true)
|
|
1502
1527
|
```
|
|
1503
1528
|
|
|
1504
|
-
Measured on the real
|
|
1505
|
-
benign follow-
|
|
1506
|
-
|
|
1529
|
+
Measured on 20.21.14 through the real handler, on the local 9b with cloud off:
|
|
1530
|
+
- Every benign follow-up that reached the local 9b with its conversation
|
|
1531
|
+
attached was answered correctly. Without the conversation, most answers were
|
|
1532
|
+
invented.
|
|
1533
|
+
- The on-device screen still refuses too many benign follow-ups: 11 of 26 in
|
|
1534
|
+
our tests. Each refusal names its reason. Cutting these false refusals is
|
|
1535
|
+
the current work.
|
|
1536
|
+
- Every reserved multi-turn probe was refused before any generation (11 of 11).
|
|
1507
1537
|
</details>
|
|
1508
1538
|
|
|
1509
1539
|
| Mode | Think | Model | Use case |
|
|
@@ -1556,6 +1586,10 @@ host, or run `prism savings` from a terminal — `--period all|month|week|sessio
|
|
|
1556
1586
|
prism-coder:9b: 41 call(s), ~505K tokens
|
|
1557
1587
|
prism-coder:4b: 12 call(s), ~4.8K tokens
|
|
1558
1588
|
|
|
1589
|
+
Follow-ups with your conversation:
|
|
1590
|
+
12 answered locally (12 by the 9b) · 2 refused by the on-device screen · 0 sent to cloud
|
|
1591
|
+
Refusals by stage: follow-up alone 1 · turns together 1
|
|
1592
|
+
|
|
1559
1593
|
Counts tokens a local model handled instead of your cloud model. On the token
|
|
1560
1594
|
axis, the token count is measured — a floor, with known undercounts listed
|
|
1561
1595
|
when present. On the displacement axis, prism cannot observe the call your
|
|
@@ -1577,6 +1611,21 @@ the same tokens; and most users are on flat plans where a currency figure means
|
|
|
1577
1611
|
nothing at all. Tokens are the one unit prism measured itself. If you know your
|
|
1578
1612
|
own effective rate, multiply — the split is printed for exactly that reason.
|
|
1579
1613
|
|
|
1614
|
+
**Follow-ups.** Calls that carried your conversation (`messages`) get their
|
|
1615
|
+
own line:
|
|
1616
|
+
- how many the local model answered, and how many of those the 9b answered;
|
|
1617
|
+
- how many the on-device screen refused, and at which stage (the follow-up read
|
|
1618
|
+
alone, an earlier turn read alone, or the turns read together);
|
|
1619
|
+
- how many went to the cloud.
|
|
1620
|
+
|
|
1621
|
+
A refused follow-up went back to your host instead of being answered locally,
|
|
1622
|
+
so this line shows how much of your follow-up work local serving actually
|
|
1623
|
+
took. Refusals because your plan does not include multi-turn history, or the
|
|
1624
|
+
history is over its cap, are counted on their own line. They are recorded only
|
|
1625
|
+
when the host asks for a report (`escalation: "report"`); otherwise they fail
|
|
1626
|
+
before anything is recorded. The line appears for the `week`, `month`, `all`
|
|
1627
|
+
and `--days` views, which read the durable ledger.
|
|
1628
|
+
|
|
1580
1629
|
Refused calls are excluded, the VS Code panel-playground share is disclosed
|
|
1581
1630
|
separately, and the known sources of undercount are listed inline rather than
|
|
1582
1631
|
left implicit — so the durable (`week`/`month`/`all`/`--days`) headline is a
|
|
@@ -1682,10 +1731,10 @@ command surface, safety model, and verified acceptance cases.
|
|
|
1682
1731
|
<img src="docs/scm_search_cli.jpg" alt="prism search — semantic code search with relevance scores" width="500" />
|
|
1683
1732
|
</p>
|
|
1684
1733
|
|
|
1685
|
-
### `prism review` — AI code review with
|
|
1734
|
+
### `prism review` — AI code review with security and privacy checks
|
|
1686
1735
|
|
|
1687
1736
|
<p align="center">
|
|
1688
|
-
<img src="docs/scm_review_cli.jpg" alt="prism review — AI code review with security and
|
|
1737
|
+
<img src="docs/scm_review_cli.jpg" alt="prism review — AI code review with security and privacy findings" width="400" />
|
|
1689
1738
|
</p>
|
|
1690
1739
|
|
|
1691
1740
|
### `prism scan` — security scanner for secrets, Dockerfiles, licenses
|
|
@@ -1745,9 +1794,9 @@ AI chat, voice input, SOAP note generator, team collaboration, and video calls
|
|
|
1745
1794
|
<summary>Feature details</summary>
|
|
1746
1795
|
|
|
1747
1796
|
- **AI**: Chat participant (`@synalux`), multi-agent pipeline, voice input, model switching, 10 tones
|
|
1748
|
-
- **Clinical**: SOAP note generator, role-based access, document signing
|
|
1797
|
+
- **Clinical**: SOAP note generator, role-based access, document signing
|
|
1749
1798
|
- **Collaboration**: Team chat, DMs, video calls, customer board, visual builder, DevContainers
|
|
1750
|
-
- **Privacy**: Local Ollama by default. `preferLocal=true` tries local first.
|
|
1799
|
+
- **Privacy**: Local Ollama by default. `preferLocal=true` tries local first.
|
|
1751
1800
|
</details>
|
|
1752
1801
|
|
|
1753
1802
|
### Prism AAC
|
|
@@ -1846,25 +1895,37 @@ It reads `~/.prism-mcp/data.db` and POSTs entries to the portal. Ledger entries
|
|
|
1846
1895
|
|
|
1847
1896
|
| Feature | Details |
|
|
1848
1897
|
|---------|---------|
|
|
1849
|
-
| Local inference | Direct Ollama use is unrestricted; automatic `prism_infer` routing selects up to
|
|
1898
|
+
| Local inference | Direct Ollama use is unrestricted; automatic `prism_infer` routing selects up to 27B, as far as your machine's memory allows |
|
|
1850
1899
|
| Session memory | Persistent sessions, handoffs, ledger — all local SQLite |
|
|
1851
1900
|
| Knowledge search | Semantic search across session history |
|
|
1852
1901
|
| Skills | All skills available locally (run `sync-skills.sh` to populate) |
|
|
1853
1902
|
| Drift detection | Server-side GATE 5 reminders |
|
|
1854
1903
|
|
|
1855
|
-
###
|
|
1904
|
+
### Free account (Synalux sign-in, $0)
|
|
1856
1905
|
|
|
1857
1906
|
Everything in Free, plus:
|
|
1858
1907
|
|
|
1859
1908
|
| Feature | Details |
|
|
1860
1909
|
|---------|---------|
|
|
1861
|
-
|
|
|
1910
|
+
| Multi-turn `prism_infer` | Conversation history carried across calls; each local answer is checked on your device before it is served |
|
|
1911
|
+
| Cloud answers | 20 a day from Gemini when a local answer cannot be used: 3.5 Flash-Lite in a conversation, 3.6 Flash for a single prompt |
|
|
1912
|
+
|
|
1913
|
+
Sign in from `prism dashboard` under **Account**.
|
|
1914
|
+
|
|
1915
|
+
### Pro ($15/mo) and Team ($20/seat/mo, 2-seat minimum)
|
|
1916
|
+
|
|
1917
|
+
Everything in the free account, plus:
|
|
1918
|
+
|
|
1919
|
+
| Feature | Details |
|
|
1920
|
+
|---------|---------|
|
|
1921
|
+
| Cloud answers | 1,000 a month (Pro; 500 during the 14-day trial) or 1,500 a month per seat (Team), at most 200 a day, from Gemini 3.6 Flash; a cloud confirmation counts as one |
|
|
1922
|
+
| Answer confirmation | A local answer that passed the on-device check is also confirmed by Synalux, on a copy with names, dates and other identifiers replaced on your device |
|
|
1862
1923
|
| Skill routing | Portal resolves which skills to load based on your project and prompt |
|
|
1863
1924
|
| Cross-device memory | Supabase cloud sync — sessions survive across machines |
|
|
1864
1925
|
| Grounding verifier | L3 NLI verification on model outputs |
|
|
1865
|
-
| Team features | Multi-agent Hivemind, workspace collaboration |
|
|
1926
|
+
| Team features | Multi-agent Hivemind, workspace collaboration, account and team skills |
|
|
1866
1927
|
|
|
1867
|
-
The paid
|
|
1928
|
+
The paid tiers add **intelligent routing** — the Synalux portal determines which skills are relevant to your current project and prompt, so your agent gets domain expertise (stripe patterns, training protocols, clinical standards) instead of loading everything. Free users with the repo can run `sync-skills.sh` to populate all skills locally; paid routing adds project-aware and prompt-aware selection.
|
|
1868
1929
|
|
|
1869
1930
|
- Contributions require signing the [CLA](./CLA.md).
|
|
1870
1931
|
- "Prism" and "Synalux" are trade names of Synalux LLC; the Apache license does
|
package/dist/config.js
CHANGED
|
@@ -14,6 +14,9 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
* is needed. Get one at https://brave.com/search/api/
|
|
15
15
|
* GOOGLE_API_KEY — (optional) API key for Google AI Studio / Gemini. Enables paper analysis.
|
|
16
16
|
* BRAVE_ANSWERS_API_KEY — (optional) API key for Brave Answers (AI grounding). Enables brave_answers tool.
|
|
17
|
+
* YDC_API_KEY — (optional) API key for You.com Search. When set, the optional
|
|
18
|
+
* youcom_web_search tool is registered as an alternative web search
|
|
19
|
+
* provider. Get one at https://you.com/platform/api-keys
|
|
17
20
|
* SUPABASE_URL — (optional) Your Supabase project URL. Enables session memory tools.
|
|
18
21
|
* SUPABASE_KEY — (optional) Your Supabase anon/service key. Enables session memory tools.
|
|
19
22
|
* PRISM_USER_ID — (optional) Unique tenant ID for multi-user Supabase instances.
|
|
@@ -72,6 +75,18 @@ export const SEMANTIC_SCHOLAR_API_KEY = process.env.SEMANTIC_SCHOLAR_API_KEY;
|
|
|
72
75
|
if (!BRAVE_ANSWERS_API_KEY && process.env.PRISM_DEBUG_LOGGING === "true") {
|
|
73
76
|
console.error("Warning: BRAVE_ANSWERS_API_KEY environment variable is missing. Brave Answers tool will be unavailable.");
|
|
74
77
|
}
|
|
78
|
+
// ─── Optional: You.com Search API Key ─────────────────────────
|
|
79
|
+
// Used by the youcom_web_search tool. When set, registers an optional
|
|
80
|
+
// web search tool that uses You.com's search API.
|
|
81
|
+
// Get a key at https://you.com/platform/api-keys
|
|
82
|
+
// You.com's own docs use YDC_API_KEY: https://you.com/docs/using-the-api/authentication
|
|
83
|
+
// Trimmed, and blank means unset: a whitespace-only value would otherwise
|
|
84
|
+
// register the tool while every call reports the key as not configured.
|
|
85
|
+
export const YDC_API_KEY = process.env.YDC_API_KEY?.trim() || undefined;
|
|
86
|
+
if (process.env.PRISM_DEBUG_LOGGING === "true") {
|
|
87
|
+
// Log presence or absence — non-critical, just informational
|
|
88
|
+
console.error(`[Prism] You.com search: ${YDC_API_KEY ? "configured" : "not configured (youcom_web_search tool disabled)"}`);
|
|
89
|
+
}
|
|
75
90
|
// ─── Optional: Voyage AI Embeddings ──────────────────────────
|
|
76
91
|
// Set embedding_provider=voyage to enable. Requires VOYAGE_API_KEY.
|
|
77
92
|
export const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY; // embedding_provider=voyage
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { invalidateEntitlements } from "../utils/entitlements.js";
|
|
2
|
+
import { clearInferencePolicies } from "../utils/inferencePolicy.js";
|
|
2
3
|
import { getSynaluxJwt, invalidateSynaluxJwt } from "../utils/synaluxJwt.js";
|
|
3
4
|
import { resolvePortalBaseUrl, usablePortalKey } from "../utils/synaluxSearch.js";
|
|
4
5
|
import { isSynaluxSignedOut, setSynaluxSignedOut } from "../utils/synaluxCredentialState.js";
|
|
@@ -31,6 +32,7 @@ const defaultDeps = {
|
|
|
31
32
|
getJwt: getSynaluxJwt,
|
|
32
33
|
invalidateJwt: invalidateSynaluxJwt,
|
|
33
34
|
invalidateEntitlements,
|
|
35
|
+
clearInferencePolicies,
|
|
34
36
|
closeStorage: async () => (await import("../storage/index.js")).closeStorage(),
|
|
35
37
|
resolvePortalBaseUrl,
|
|
36
38
|
usablePortalKey,
|
|
@@ -139,6 +141,7 @@ function normalizeAccount(value, origin) {
|
|
|
139
141
|
role_key: typeof raw.role_key === "string" ? raw.role_key.slice(0, 80) : null,
|
|
140
142
|
plan: plan,
|
|
141
143
|
subscription_plan: subscriptionPlan,
|
|
144
|
+
prism_plan: raw.prism_plan === "pro" || raw.prism_plan === "team" ? raw.prism_plan : null,
|
|
142
145
|
plan_source: raw.plan_source === "managed" ? "managed" : "stripe",
|
|
143
146
|
billing_status: billingStatus,
|
|
144
147
|
trial_ends_at: billingStatus === "trialing" || billingStatus === "sync_pending" ? trialEndsAt : null,
|
|
@@ -245,6 +248,7 @@ export async function connectDashboardAccount(code, overrides = {}) {
|
|
|
245
248
|
process.env.PRISM_SYNALUX_API_KEY = token;
|
|
246
249
|
deps.invalidateJwt();
|
|
247
250
|
deps.invalidateEntitlements();
|
|
251
|
+
deps.clearInferencePolicies();
|
|
248
252
|
await deps.closeStorage();
|
|
249
253
|
return accountWithJwt(deps, origin);
|
|
250
254
|
});
|
|
@@ -298,6 +302,7 @@ export async function signOutDashboardAccount(overrides = {}) {
|
|
|
298
302
|
delete process.env.PRISM_SYNALUX_API_KEY;
|
|
299
303
|
deps.invalidateJwt();
|
|
300
304
|
deps.invalidateEntitlements();
|
|
305
|
+
deps.clearInferencePolicies();
|
|
301
306
|
await deps.setSetting("PRISM_SYNALUX_SIGNED_OUT", "true");
|
|
302
307
|
await deps.setSetting("PRISM_SYNALUX_API_KEY", "");
|
|
303
308
|
await deps.closeStorage();
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -1428,7 +1428,7 @@ export function renderDashboardHTML(version) {
|
|
|
1428
1428
|
</div>
|
|
1429
1429
|
|
|
1430
1430
|
<div style="margin-top:1.25rem;padding:0.85rem;border-radius:var(--radius-sm);background:rgba(16,185,129,0.08);border:1px solid rgba(16,185,129,0.2);font-size:0.78rem;color:var(--text-secondary)">
|
|
1431
|
-
✅ <strong>All 6 enforcement layers are active.</strong> Ethics enforcement is NOT tier-gated — applies equally to Free,
|
|
1431
|
+
✅ <strong>All 6 enforcement layers are active.</strong> Ethics enforcement is NOT tier-gated — applies equally to Free, the free account, Pro and Team.
|
|
1432
1432
|
<a href="/ethics" style="color:var(--accent-purple);margin-left:0.5rem">View full policy →</a>
|
|
1433
1433
|
</div>
|
|
1434
1434
|
</div>
|
|
@@ -2109,13 +2109,22 @@ var _searchBoost = document.getElementById('searchContextBoost');
|
|
|
2109
2109
|
if (_searchBoost)
|
|
2110
2110
|
_searchBoost.addEventListener('change', performSearch);
|
|
2111
2111
|
var currentAccount = null;
|
|
2112
|
+
// Prism's own Pro and Team arrive as the tier they include (plan) plus
|
|
2113
|
+
// prism_plan; show their names when the tier is theirs.
|
|
2114
|
+
function accountDisplayPlan(account) {
|
|
2115
|
+
if (account && account.prism_plan === 'pro' && account.plan === 'standard') return 'pro';
|
|
2116
|
+
if (account && account.prism_plan === 'team' && account.plan === 'advanced') return 'team';
|
|
2117
|
+
return account ? account.plan : 'free';
|
|
2118
|
+
}
|
|
2112
2119
|
function accountPlanLabel(plan) {
|
|
2113
|
-
var labels = { free: 'Free', standard: 'Standard', advanced: 'Advanced', enterprise: 'Enterprise' };
|
|
2120
|
+
var labels = { free: 'Free', pro: 'Pro', team: 'Team', standard: 'Standard', advanced: 'Advanced', enterprise: 'Enterprise' };
|
|
2114
2121
|
return labels[plan] || 'Free';
|
|
2115
2122
|
}
|
|
2116
2123
|
function accountPlanSummary(plan) {
|
|
2117
2124
|
var summaries = {
|
|
2118
2125
|
free: 'Local memory, local models, and the Mind Palace dashboard remain available without an account.',
|
|
2126
|
+
pro: 'Prism Pro is active: cloud answers, answer confirmation, sync, and the full skill library.',
|
|
2127
|
+
team: 'Prism Team is active for this seat: cloud answers, answer confirmation, sync, and team skills.',
|
|
2119
2128
|
standard: 'Cloud sync and paid Prism services are active for this account.',
|
|
2120
2129
|
advanced: 'Advanced cloud capacity and team features are active for this account.',
|
|
2121
2130
|
enterprise: 'Enterprise capacity, governance, and support are active for this account.'
|
|
@@ -2141,13 +2150,13 @@ function accountBillingBadge(account) {
|
|
|
2141
2150
|
function accountSubscriptionSummary(account) {
|
|
2142
2151
|
if (account.billing_status === 'trialing') {
|
|
2143
2152
|
var end = accountTrialEndLabel(account.trial_ends_at);
|
|
2144
|
-
return accountPlanLabel(account
|
|
2153
|
+
return accountPlanLabel(accountDisplayPlan(account)) + ' trial is active' + (end ? ' through ' + end : '') +
|
|
2145
2154
|
'. Add payment details before the trial ends to continue; otherwise it cancels automatically.';
|
|
2146
2155
|
}
|
|
2147
2156
|
if (['past_due', 'unpaid', 'incomplete', 'paused'].indexOf(account.billing_status) >= 0) {
|
|
2148
2157
|
var billedPlan = account.subscription_plan
|
|
2149
2158
|
? accountPlanLabel(account.subscription_plan) + ' plan'
|
|
2150
|
-
: account.plan === 'free' ? 'paid subscription' : accountPlanLabel(account
|
|
2159
|
+
: account.plan === 'free' ? 'paid subscription' : accountPlanLabel(accountDisplayPlan(account)) + ' plan';
|
|
2151
2160
|
return 'Your ' + billedPlan + ' needs billing attention. Update payment details to restore or keep cloud features active.';
|
|
2152
2161
|
}
|
|
2153
2162
|
if (account.billing_status === 'sync_pending') {
|
|
@@ -2155,7 +2164,7 @@ function accountSubscriptionSummary(account) {
|
|
|
2155
2164
|
var syncTrialEnd = accountTrialEndLabel(account.trial_ends_at);
|
|
2156
2165
|
return 'Stripe confirms your ' + syncingPlan + ' subscription' +
|
|
2157
2166
|
(syncTrialEnd ? ' with a trial through ' + syncTrialEnd : '') +
|
|
2158
|
-
'. Synalux is updating access; current access remains ' + accountPlanLabel(account
|
|
2167
|
+
'. Synalux is updating access; current access remains ' + accountPlanLabel(accountDisplayPlan(account)) + '. Retry shortly or manage the subscription.';
|
|
2159
2168
|
}
|
|
2160
2169
|
if (account.billing_status === 'canceled' || account.billing_status === 'incomplete_expired') {
|
|
2161
2170
|
return 'This paid subscription is no longer active. Local Prism Free remains available.';
|
|
@@ -2163,7 +2172,7 @@ function accountSubscriptionSummary(account) {
|
|
|
2163
2172
|
if (account.billing_status === 'unknown') {
|
|
2164
2173
|
return 'Prism could not verify the current trial or payment status. Open billing to review the subscription; local features remain available.';
|
|
2165
2174
|
}
|
|
2166
|
-
return accountPlanSummary(account
|
|
2175
|
+
return accountPlanSummary(accountDisplayPlan(account));
|
|
2167
2176
|
}
|
|
2168
2177
|
function renderPlanLadder(plan) {
|
|
2169
2178
|
var tiers = [
|
|
@@ -2199,7 +2208,7 @@ function renderAccountChip(account, error) {
|
|
|
2199
2208
|
? planStatus
|
|
2200
2209
|
: account.plan === 'free' && planStatus === 'Payment due'
|
|
2201
2210
|
? planStatus
|
|
2202
|
-
: accountPlanLabel(account
|
|
2211
|
+
: accountPlanLabel(accountDisplayPlan(account)) + planStatus;
|
|
2203
2212
|
chip.innerHTML = '<span class="role-icon">🤖</span><span class="identity-label">' + escapeHtml(account.name || 'Synalux user') + '</span>' +
|
|
2204
2213
|
'<span class="plan-mini' + planStatusClass + '">' + escapeHtml(chipPlanLabel) + '</span>';
|
|
2205
2214
|
}
|
|
@@ -2249,7 +2258,7 @@ function renderAccountPanel(account, error) {
|
|
|
2249
2258
|
account.billing && account.billing.action === 'included' ? 'View plans' : 'Start 14-day trial';
|
|
2250
2259
|
managedBadge = account.plan_source === 'managed' ? '<span class="account-badge managed">Managed</span>' : '';
|
|
2251
2260
|
panel.innerHTML = '<div class="account-card"><div class="account-card-head"><div>' +
|
|
2252
|
-
'<div class="account-badges"><span class="account-badge">' + escapeHtml(accountPlanLabel(account
|
|
2261
|
+
'<div class="account-badges"><span class="account-badge">' + escapeHtml(accountPlanLabel(accountDisplayPlan(account))) + '</span>' +
|
|
2253
2262
|
'<span class="account-badge role">' + escapeHtml(role) + '</span>' + managedBadge + accountBillingBadge(account) + '</div>' +
|
|
2254
2263
|
'<div class="account-name">' + escapeHtml(account.name || 'Synalux user') + '</div>' +
|
|
2255
2264
|
'<div class="account-summary">' + escapeHtml(accountSubscriptionSummary(account)) + '</div></div></div>' +
|
package/dist/server.js
CHANGED
|
@@ -60,7 +60,7 @@ ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequ
|
|
|
60
60
|
// Claude Desktop that the attached resource has changed.
|
|
61
61
|
// Without this, the paperclipped context becomes stale.
|
|
62
62
|
SubscribeRequestSchema, UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
63
|
-
import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID, PRISM_ENABLE_HIVEMIND, WATCHDOG_INTERVAL_MS, WATCHDOG_STALE_MIN, WATCHDOG_FROZEN_MIN, WATCHDOG_OFFLINE_MIN, WATCHDOG_LOOP_THRESHOLD, PRISM_SCHEDULER_ENABLED, PRISM_SCHEDULER_INTERVAL_MS, PRISM_HDC_ENABLED, PRISM_TASK_ROUTER_ENABLED_ENV, PRISM_DARK_FACTORY_ENABLED, } from "./config.js";
|
|
63
|
+
import { SERVER_CONFIG, SESSION_MEMORY_ENABLED, PRISM_USER_ID, PRISM_ENABLE_HIVEMIND, WATCHDOG_INTERVAL_MS, WATCHDOG_STALE_MIN, WATCHDOG_FROZEN_MIN, WATCHDOG_OFFLINE_MIN, WATCHDOG_LOOP_THRESHOLD, PRISM_SCHEDULER_ENABLED, PRISM_SCHEDULER_INTERVAL_MS, PRISM_HDC_ENABLED, PRISM_TASK_ROUTER_ENABLED_ENV, PRISM_DARK_FACTORY_ENABLED, YDC_API_KEY, } from "./config.js";
|
|
64
64
|
import { startWatchdog, drainAlerts } from "./hivemindWatchdog.js";
|
|
65
65
|
import { startScheduler } from "./backgroundScheduler.js";
|
|
66
66
|
import { startDarkFactoryRunner } from "./darkfactory/runner.js";
|
|
@@ -91,7 +91,7 @@ import { triggerSkillManifestSync } from "./skillManifestSync.js";
|
|
|
91
91
|
import { EVIDENCE_WORKFLOW_POLICY_TEXT } from "./evidenceWorkflowPolicy.js";
|
|
92
92
|
import { LOCAL_FIRST_POLICY_TEXT } from "./localFirstPolicy.js";
|
|
93
93
|
// ─── Import Tool Definitions (schemas) and Handlers (implementations) ─────
|
|
94
|
-
import { WEB_SEARCH_TOOL, BRAVE_WEB_SEARCH_CODE_MODE_TOOL, LOCAL_SEARCH_TOOL, BRAVE_LOCAL_SEARCH_CODE_MODE_TOOL, CODE_MODE_TRANSFORM_TOOL, BRAVE_ANSWERS_TOOL, RESEARCH_PAPER_ANALYSIS_TOOL, webSearchHandler, braveWebSearchCodeModeHandler, localSearchHandler, braveLocalSearchCodeModeHandler, codeModeTransformHandler, braveAnswersHandler, researchPaperAnalysisHandler, } from "./tools/index.js";
|
|
94
|
+
import { WEB_SEARCH_TOOL, BRAVE_WEB_SEARCH_CODE_MODE_TOOL, LOCAL_SEARCH_TOOL, BRAVE_LOCAL_SEARCH_CODE_MODE_TOOL, CODE_MODE_TRANSFORM_TOOL, BRAVE_ANSWERS_TOOL, RESEARCH_PAPER_ANALYSIS_TOOL, YOUCOM_WEB_SEARCH_TOOL, webSearchHandler, braveWebSearchCodeModeHandler, localSearchHandler, braveLocalSearchCodeModeHandler, codeModeTransformHandler, braveAnswersHandler, researchPaperAnalysisHandler, youcomWebSearchHandler, } from "./tools/index.js";
|
|
95
95
|
// Session memory tools — only used if Supabase is configured
|
|
96
96
|
import { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, SESSION_BOOTSTRAP_TOOL, SESSION_ROUTE_PROMPT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL,
|
|
97
97
|
// ─── v0.4.0: New tool definitions (Enhancements #2 and #4) ───
|
|
@@ -180,6 +180,8 @@ const BASE_TOOLS = [
|
|
|
180
180
|
BRAVE_ANSWERS_TOOL, // brave_answers — AI-grounded answers
|
|
181
181
|
RESEARCH_PAPER_ANALYSIS_TOOL, // gemini_research_paper_analysis — paper analysis
|
|
182
182
|
PRISM_INFER_TOOL, // prism_infer — local-first inference (token-saving cascade)
|
|
183
|
+
// youcom_web_search — optional You.com web search, gated on YDC_API_KEY
|
|
184
|
+
...(YDC_API_KEY ? [YOUCOM_WEB_SEARCH_TOOL] : []),
|
|
183
185
|
];
|
|
184
186
|
// Hook-free first-turn startup is owned by session_bootstrap. Its static tool
|
|
185
187
|
// description is surfaced by native MCP hosts before any tool call and keeps
|
|
@@ -748,6 +750,10 @@ export function createServer() {
|
|
|
748
750
|
case "prism_infer":
|
|
749
751
|
result = await prismInferHandler(args);
|
|
750
752
|
break;
|
|
753
|
+
// ── Optional You.com Web Search (only when YDC_API_KEY is set) ──
|
|
754
|
+
case "youcom_web_search":
|
|
755
|
+
result = await youcomWebSearchHandler(args);
|
|
756
|
+
break;
|
|
751
757
|
// ── Session Memory Tools (only callable when Supabase is configured) ──
|
|
752
758
|
// REVIEWER NOTE: Even though these tools won't appear in the
|
|
753
759
|
// tool list without Supabase, we still guard each handler call
|
|
@@ -42,8 +42,8 @@ const LEDGER_UNAVAILABLE_ERROR = "Inference metrics ledger is unavailable";
|
|
|
42
42
|
const INSERT_METRIC_SQL = `INSERT OR IGNORE INTO infer_metrics
|
|
43
43
|
(ts, caller, mode, backend, model, used_cloud, gate_outcome,
|
|
44
44
|
refusal_reason, prompt_tokens, completion_tokens, latency_ms, ram_free_mb,
|
|
45
|
-
source_event_id, history_turns, refusal_layer)
|
|
46
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
45
|
+
source_event_id, history_turns, refusal_layer, layer1_second_read)
|
|
46
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
47
47
|
function closeClient(context) {
|
|
48
48
|
const activeClient = client;
|
|
49
49
|
client = null;
|
|
@@ -85,7 +85,8 @@ function ensureTable() {
|
|
|
85
85
|
ram_free_mb INTEGER,
|
|
86
86
|
source_event_id TEXT,
|
|
87
87
|
history_turns INTEGER,
|
|
88
|
-
refusal_layer TEXT
|
|
88
|
+
refusal_layer TEXT,
|
|
89
|
+
layer1_second_read TEXT
|
|
89
90
|
)`);
|
|
90
91
|
// Existing ledgers predate external panel-spool ingestion. SQLite has
|
|
91
92
|
// no ADD COLUMN IF NOT EXISTS, so use the repository's established
|
|
@@ -94,6 +95,7 @@ function ensureTable() {
|
|
|
94
95
|
"source_event_id TEXT",
|
|
95
96
|
"history_turns INTEGER",
|
|
96
97
|
"refusal_layer TEXT",
|
|
98
|
+
"layer1_second_read TEXT",
|
|
97
99
|
]) {
|
|
98
100
|
try {
|
|
99
101
|
await client.execute(`ALTER TABLE infer_metrics ADD COLUMN ${column}`);
|
|
@@ -158,6 +160,7 @@ function metricArgs(row) {
|
|
|
158
160
|
row.completion_tokens ?? null, row.latency_ms ?? null,
|
|
159
161
|
row.ram_free_mb ?? null, row.source_event_id ?? null,
|
|
160
162
|
row.history_turns ?? null, row.refusal_layer ?? null,
|
|
163
|
+
row.layer1_second_read ?? null,
|
|
161
164
|
];
|
|
162
165
|
}
|
|
163
166
|
/** Aggregate all persisted rows (optionally since a timestamp). */
|
|
@@ -284,6 +287,32 @@ export async function queryLocalSavings(sinceTs) {
|
|
|
284
287
|
GROUP BY COALESCE(model, backend)`,
|
|
285
288
|
args: whereArgs,
|
|
286
289
|
});
|
|
290
|
+
const followWhere = `WHERE history_turns > 0${sinceTs != null ? " AND ts >= ?" : ""}`;
|
|
291
|
+
const PLAN_REFUSAL = `COALESCE(refusal_reason, '') IN ('multi_turn_not_in_plan', 'history_over_plan_cap')`;
|
|
292
|
+
// A 9b model: '9b' right after a non-digit, so a future '19b' is not counted.
|
|
293
|
+
const NINE_B = `(LOWER(COALESCE(model, backend)) GLOB '*[^0-9]9b*' OR LOWER(COALESCE(model, backend)) GLOB '9b*')`;
|
|
294
|
+
const follow = await client.execute({
|
|
295
|
+
sql: `SELECT
|
|
296
|
+
SUM(CASE WHEN ${SERVED_LOCAL} THEN 1 ELSE 0 END) AS served,
|
|
297
|
+
SUM(CASE WHEN ${SERVED_LOCAL} AND ${NINE_B} THEN 1 ELSE 0 END) AS served_9b,
|
|
298
|
+
SUM(CASE WHEN used_cloud = 1 THEN 1 ELSE 0 END) AS cloud,
|
|
299
|
+
SUM(CASE WHEN used_cloud = 0 AND NOT (${SERVED_LOCAL}) AND NOT (${PLAN_REFUSAL}) THEN 1 ELSE 0 END) AS refused,
|
|
300
|
+
SUM(CASE WHEN used_cloud = 0 AND NOT (${SERVED_LOCAL}) AND ${PLAN_REFUSAL} THEN 1 ELSE 0 END) AS refused_by_plan
|
|
301
|
+
FROM infer_metrics ${followWhere}`,
|
|
302
|
+
args: whereArgs,
|
|
303
|
+
});
|
|
304
|
+
const followLayers = await client.execute({
|
|
305
|
+
sql: `SELECT COALESCE(refusal_layer, 'unrecorded') AS layer, COUNT(*) AS n
|
|
306
|
+
FROM infer_metrics ${followWhere} AND used_cloud = 0 AND NOT (${SERVED_LOCAL})
|
|
307
|
+
AND NOT (${PLAN_REFUSAL})
|
|
308
|
+
GROUP BY COALESCE(refusal_layer, 'unrecorded')`,
|
|
309
|
+
args: whereArgs,
|
|
310
|
+
});
|
|
311
|
+
const f = follow.rows[0];
|
|
312
|
+
const refused_by_layer = {};
|
|
313
|
+
for (const row of followLayers.rows) {
|
|
314
|
+
refused_by_layer[String(row.layer)] = Number(row.n ?? 0);
|
|
315
|
+
}
|
|
287
316
|
const r = agg.rows[0];
|
|
288
317
|
const by_model = {};
|
|
289
318
|
for (const row of byM.rows) {
|
|
@@ -310,6 +339,14 @@ export async function queryLocalSavings(sinceTs) {
|
|
|
310
339
|
first_ts: r.first_ts == null ? null : Number(r.first_ts),
|
|
311
340
|
last_ts: r.last_ts == null ? null : Number(r.last_ts),
|
|
312
341
|
by_model,
|
|
342
|
+
followups: {
|
|
343
|
+
served_local: Number(f.served ?? 0),
|
|
344
|
+
served_local_9b: Number(f.served_9b ?? 0),
|
|
345
|
+
refused: Number(f.refused ?? 0),
|
|
346
|
+
refused_by_plan: Number(f.refused_by_plan ?? 0),
|
|
347
|
+
cloud: Number(f.cloud ?? 0),
|
|
348
|
+
refused_by_layer,
|
|
349
|
+
},
|
|
313
350
|
};
|
|
314
351
|
}
|
|
315
352
|
catch (e) {
|