prism-mcp-server 20.21.19 → 20.21.21
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 +19 -0
- package/dist/dashboard/ledgerReader.js +4 -2
- package/dist/dashboard/readMessages.js +48 -0
- package/dist/dashboard/server.js +13 -10
- package/dist/dashboard/ui.js +231 -386
- package/dist/server.js +2 -8
- package/dist/storage/portalContracts.js +23 -0
- package/dist/storage/sqlite.js +7 -4
- package/dist/storage/supabase.js +37 -29
- package/dist/storage/synalux.js +14 -1
- package/dist/tools/hygieneHandlers.js +7 -2
- package/dist/tools/ledgerHandlers.js +27 -0
- package/docs/prism-browser.md +7 -5
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -764,14 +764,8 @@ export function createServer() {
|
|
|
764
764
|
if (!SESSION_MEMORY_ENABLED)
|
|
765
765
|
throw new Error("Session memory not configured. Set SUPABASE_URL and SUPABASE_KEY.");
|
|
766
766
|
result = await sessionSaveLedgerHandler(args);
|
|
767
|
-
// GATE 5:
|
|
768
|
-
|
|
769
|
-
const cid = args?.conversation_id;
|
|
770
|
-
if (cid) {
|
|
771
|
-
const { noteDriftCheck } = await import("./session/sessionContext.js");
|
|
772
|
-
noteDriftCheck(cid);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
767
|
+
// GATE 5: the handler resets the drift timer itself, and only when
|
|
768
|
+
// it wrote an entry (not on a refusal or the greeting-only skip).
|
|
775
769
|
// Savings sync (paid, opt-in, fail-soft): session end is the natural
|
|
776
770
|
// push point. pushSavings gates itself — disabled/free/no-jwt all
|
|
777
771
|
// return without a request, and nothing here awaits it.
|
|
@@ -17,6 +17,29 @@
|
|
|
17
17
|
* 3. Add a schema-contract test in synalux-portal-contract.test.ts.
|
|
18
18
|
*/
|
|
19
19
|
import { z } from "zod";
|
|
20
|
+
import { ANALYTICS_DATE_PATTERN } from "../dashboard/readMessages.js";
|
|
21
|
+
const AnalyticsCountSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
22
|
+
const UTC_DAY_MS = 24 * 60 * 60 * 1000;
|
|
23
|
+
export const ProjectAnalyticsSchema = z.object({
|
|
24
|
+
totalEntries: AnalyticsCountSchema,
|
|
25
|
+
totalRollups: AnalyticsCountSchema,
|
|
26
|
+
rollupSavings: AnalyticsCountSchema,
|
|
27
|
+
avgSummaryLength: z.number().nonnegative().finite(),
|
|
28
|
+
sessionsByDay: z.array(z.object({
|
|
29
|
+
date: z.string().regex(ANALYTICS_DATE_PATTERN), count: AnalyticsCountSchema,
|
|
30
|
+
})).length(14),
|
|
31
|
+
}).refine(value => value.totalRollups <= value.totalEntries
|
|
32
|
+
&& value.sessionsByDay.reduce((sum, day) => sum + day.count, 0) <= value.totalEntries - value.totalRollups
|
|
33
|
+
&& value.sessionsByDay.every((day, index, days) => {
|
|
34
|
+
const time = Date.parse(day.date);
|
|
35
|
+
return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === day.date
|
|
36
|
+
&& (index === 0 || time - Date.parse(days[index - 1].date) === UTC_DAY_MS);
|
|
37
|
+
}));
|
|
38
|
+
export const ProjectAnalyticsResponseSchema = z.object({
|
|
39
|
+
status: z.literal('success'),
|
|
40
|
+
project: z.string().min(1).max(100),
|
|
41
|
+
analytics: ProjectAnalyticsSchema,
|
|
42
|
+
});
|
|
20
43
|
// ─── knowledge_search ────────────────────────────────────────────
|
|
21
44
|
export const KnowledgeSearchRequestSchema = z.object({
|
|
22
45
|
action: z.literal("knowledge_search"),
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -1824,6 +1824,7 @@ export class SqliteStorage {
|
|
|
1824
1824
|
FROM session_ledger
|
|
1825
1825
|
WHERE user_id = ?
|
|
1826
1826
|
AND archived_at IS NULL
|
|
1827
|
+
AND deleted_at IS NULL
|
|
1827
1828
|
AND embedding IS NULL
|
|
1828
1829
|
`,
|
|
1829
1830
|
args: [userId], // bind user_id to the ? placeholder
|
|
@@ -1883,11 +1884,13 @@ export class SqliteStorage {
|
|
|
1883
1884
|
FROM session_ledger r
|
|
1884
1885
|
LEFT JOIN session_ledger a
|
|
1885
1886
|
ON a.archived_at IS NOT NULL
|
|
1887
|
+
AND a.deleted_at IS NULL
|
|
1886
1888
|
AND a.project = r.project
|
|
1887
1889
|
AND a.user_id = r.user_id
|
|
1888
1890
|
WHERE r.user_id = ?
|
|
1889
1891
|
AND r.is_rollup = 1
|
|
1890
1892
|
AND r.archived_at IS NULL
|
|
1893
|
+
AND r.deleted_at IS NULL
|
|
1891
1894
|
GROUP BY r.id
|
|
1892
1895
|
HAVING COUNT(a.id) = 0
|
|
1893
1896
|
`,
|
|
@@ -1901,12 +1904,12 @@ export class SqliteStorage {
|
|
|
1901
1904
|
sql: `
|
|
1902
1905
|
SELECT
|
|
1903
1906
|
(SELECT COUNT(*) FROM session_ledger
|
|
1904
|
-
WHERE user_id = ? AND archived_at IS NULL) as active,
|
|
1907
|
+
WHERE user_id = ? AND archived_at IS NULL AND deleted_at IS NULL) as active,
|
|
1905
1908
|
(SELECT COUNT(*) FROM session_handoffs
|
|
1906
1909
|
WHERE user_id = ?) as handoffs,
|
|
1907
1910
|
(SELECT COUNT(*) FROM session_ledger
|
|
1908
1911
|
WHERE user_id = ? AND is_rollup = 1
|
|
1909
|
-
AND archived_at IS NULL) as rollups
|
|
1912
|
+
AND archived_at IS NULL AND deleted_at IS NULL) as rollups
|
|
1910
1913
|
`,
|
|
1911
1914
|
args: [userId, userId, userId], // bind user_id 3x (one per subquery)
|
|
1912
1915
|
});
|
|
@@ -2177,7 +2180,7 @@ export class SqliteStorage {
|
|
|
2177
2180
|
COUNT(*) AS total_entries,
|
|
2178
2181
|
SUM(CASE WHEN is_rollup = 1 THEN 1 ELSE 0 END) AS total_rollups,
|
|
2179
2182
|
-- rollup_count tracks how many raw entries each rollup replaced,
|
|
2180
|
-
-- so
|
|
2183
|
+
-- so the dashboard reports how many originals were consolidated.
|
|
2181
2184
|
SUM(CASE WHEN is_rollup = 1 THEN COALESCE(rollup_count, 0) ELSE 0 END) AS rollup_savings,
|
|
2182
2185
|
COALESCE(AVG(LENGTH(summary)), 0) AS avg_summary_length
|
|
2183
2186
|
FROM session_ledger
|
|
@@ -2213,7 +2216,7 @@ export class SqliteStorage {
|
|
|
2213
2216
|
const sessionsByDay = [];
|
|
2214
2217
|
for (let i = 13; i >= 0; i--) {
|
|
2215
2218
|
const date = new Date();
|
|
2216
|
-
date.
|
|
2219
|
+
date.setUTCDate(date.getUTCDate() - i);
|
|
2217
2220
|
const dateStr = date.toISOString().slice(0, 10);
|
|
2218
2221
|
sessionsByDay.push({ date: dateStr, count: sparkMap.get(dateStr) || 0 });
|
|
2219
2222
|
}
|
package/dist/storage/supabase.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
|
|
16
16
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
17
17
|
import { debugLog } from "../utils/logger.js";
|
|
18
|
+
import { ProjectAnalyticsSchema } from './portalContracts.js';
|
|
19
|
+
import { DASHBOARD_READ_MESSAGES } from "../dashboard/readMessages.js";
|
|
18
20
|
import { PRISM_USER_ID } from "../config.js";
|
|
19
21
|
import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
|
|
20
22
|
import { runAutoMigrations } from "./supabaseMigrations.js";
|
|
@@ -154,11 +156,11 @@ export class SupabaseStorage {
|
|
|
154
156
|
};
|
|
155
157
|
}
|
|
156
158
|
catch (e) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
159
|
+
// The write failed. Reporting "updated" here told the agent its handoff
|
|
160
|
+
// was saved when nothing was; the caller turns this into an error reply.
|
|
161
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
162
|
+
debugLog("[SupabaseStorage] saveHandoff RPC failed: " + message);
|
|
163
|
+
throw new Error(`Handoff for project "${handoff.project}" was not saved: ${message}`);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
async patchHandoff(project, userId, data) {
|
|
@@ -441,12 +443,14 @@ export class SupabaseStorage {
|
|
|
441
443
|
user_id: `eq.${userId}`,
|
|
442
444
|
archived_at: "is.null",
|
|
443
445
|
embedding: "is.null",
|
|
446
|
+
deleted_at: "is.null",
|
|
444
447
|
});
|
|
445
448
|
const missingEmbeddings = Array.isArray(missingData) ? missingData.length : 0;
|
|
446
449
|
const summData = await supabaseGet("session_ledger", {
|
|
447
450
|
select: "id,project,summary",
|
|
448
451
|
user_id: `eq.${userId}`,
|
|
449
452
|
archived_at: "is.null",
|
|
453
|
+
deleted_at: "is.null",
|
|
450
454
|
});
|
|
451
455
|
const activeLedgerSummaries = (Array.isArray(summData) ? summData : []).map((r) => ({
|
|
452
456
|
id: r.id,
|
|
@@ -463,6 +467,7 @@ export class SupabaseStorage {
|
|
|
463
467
|
select: "project",
|
|
464
468
|
user_id: `eq.${userId}`,
|
|
465
469
|
archived_at: "is.null",
|
|
470
|
+
deleted_at: "is.null",
|
|
466
471
|
});
|
|
467
472
|
const ledgerProjects = new Set((Array.isArray(ledgerData) ? ledgerData : [])
|
|
468
473
|
.map((r) => r.project));
|
|
@@ -474,11 +479,13 @@ export class SupabaseStorage {
|
|
|
474
479
|
user_id: `eq.${userId}`,
|
|
475
480
|
is_rollup: "eq.true",
|
|
476
481
|
archived_at: "is.null",
|
|
482
|
+
deleted_at: "is.null",
|
|
477
483
|
});
|
|
478
484
|
const archivedData = await supabaseGet("session_ledger", {
|
|
479
485
|
select: "project",
|
|
480
486
|
user_id: `eq.${userId}`,
|
|
481
487
|
"archived_at": "not.is.null",
|
|
488
|
+
deleted_at: "is.null",
|
|
482
489
|
});
|
|
483
490
|
const archivedProjects = new Set((Array.isArray(archivedData) ? archivedData : [])
|
|
484
491
|
.map((r) => r.project));
|
|
@@ -633,32 +640,33 @@ export class SupabaseStorage {
|
|
|
633
640
|
}
|
|
634
641
|
// ─── v3.1: Memory Analytics ──────────────────────────────────
|
|
635
642
|
async getAnalytics(project, userId) {
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (data) {
|
|
645
|
-
return {
|
|
646
|
-
totalEntries: data.total_entries || 0,
|
|
647
|
-
totalRollups: data.total_rollups || 0,
|
|
648
|
-
rollupSavings: data.rollup_savings || 0,
|
|
649
|
-
avgSummaryLength: data.avg_summary_length || 0,
|
|
650
|
-
sessionsByDay: data.sessions_by_day || [],
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
debugLog("[SupabaseStorage] getAnalytics RPC unavailable — returning zeroed struct");
|
|
643
|
+
// An unavailable read must not look like a genuinely empty project.
|
|
644
|
+
const result = await supabaseRpc("get_project_analytics", {
|
|
645
|
+
p_project: project,
|
|
646
|
+
p_user_id: userId,
|
|
647
|
+
});
|
|
648
|
+
const data = Array.isArray(result) ? result[0] : result;
|
|
649
|
+
if (!data) {
|
|
650
|
+
throw new Error(DASHBOARD_READ_MESSAGES.analyticsUnavailable);
|
|
656
651
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
652
|
+
const analytics = {
|
|
653
|
+
totalEntries: data.total_entries, totalRollups: data.total_rollups,
|
|
654
|
+
rollupSavings: data.rollup_savings, avgSummaryLength: data.avg_summary_length,
|
|
655
|
+
sessionsByDay: data.sessions_by_day,
|
|
661
656
|
};
|
|
657
|
+
if (analytics.totalEntries === 0 && analytics.totalRollups === 0 && analytics.rollupSavings === 0
|
|
658
|
+
&& analytics.avgSummaryLength === 0 && Array.isArray(analytics.sessionsByDay) && analytics.sessionsByDay.length === 0) {
|
|
659
|
+
const now = new Date();
|
|
660
|
+
analytics.sessionsByDay = Array.from({ length: 14 }, (_, index) => {
|
|
661
|
+
const date = new Date(now);
|
|
662
|
+
date.setUTCDate(now.getUTCDate() - (13 - index));
|
|
663
|
+
return { date: date.toISOString().slice(0, 10), count: 0 };
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
const parsed = ProjectAnalyticsSchema.safeParse(analytics);
|
|
667
|
+
if (!parsed.success)
|
|
668
|
+
throw new Error(DASHBOARD_READ_MESSAGES.analyticsUnavailable);
|
|
669
|
+
return parsed.data;
|
|
662
670
|
}
|
|
663
671
|
// ─── v3.1: TTL / Automated Data Retention ────────────────────
|
|
664
672
|
async expireByTTL(project, ttlDays, userId) {
|
package/dist/storage/synalux.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* - patchLedger → POST /api/v1/prism/memory action=save_embedding
|
|
31
31
|
* - getEntriesMissingEmbeddings → POST /api/v1/prism/memory action=list_missing_embeddings
|
|
32
32
|
* - listProjects → POST /api/v1/prism/memory action=list_projects
|
|
33
|
+
* - getAnalytics → GET /api/v1/prism/analytics view=project
|
|
33
34
|
* - exportLedger → POST /api/v1/prism/memory action=export_memory (paginated)
|
|
34
35
|
*
|
|
35
36
|
* Methods still falling through to SupabaseStorage (Phase 3 Tier B+):
|
|
@@ -51,7 +52,7 @@ import { SupabaseStorage } from "./supabase.js";
|
|
|
51
52
|
import { debugLog } from "../utils/logger.js";
|
|
52
53
|
import { PRISM_SYNALUX_BASE_URL, PRISM_SYNALUX_API_KEY } from "../config.js";
|
|
53
54
|
import { isSynaluxSignedOut } from "../utils/synaluxCredentialState.js";
|
|
54
|
-
import { KnowledgeSearchRequestSchema, KnowledgeSearchResponseSchema } from "./portalContracts.js";
|
|
55
|
+
import { KnowledgeSearchRequestSchema, KnowledgeSearchResponseSchema, ProjectAnalyticsResponseSchema } from "./portalContracts.js";
|
|
55
56
|
function resolveKnowledgeScope(callerScope) {
|
|
56
57
|
if (callerScope === "user" || callerScope === "workspace") {
|
|
57
58
|
return callerScope;
|
|
@@ -758,6 +759,14 @@ export class SynaluxStorage extends SupabaseStorage {
|
|
|
758
759
|
}));
|
|
759
760
|
}
|
|
760
761
|
// ─── Health Check ─────────────────────────────────────────────
|
|
762
|
+
async getAnalytics(project, _userId) {
|
|
763
|
+
const query = new URLSearchParams({ view: 'project', project });
|
|
764
|
+
const result = ProjectAnalyticsResponseSchema.safeParse(await this.portalGet('/api/v1/prism/analytics?' + query));
|
|
765
|
+
if (!result.success || result.data.project !== project) {
|
|
766
|
+
throw new Error('Project analytics unavailable');
|
|
767
|
+
}
|
|
768
|
+
return result.data.analytics;
|
|
769
|
+
}
|
|
761
770
|
// Phase 3 Tier B: route health_check through portal. The portal
|
|
762
771
|
// returns summary counts only (no per-entry duplicate scan), so
|
|
763
772
|
// activeLedgerSummaries is returned empty — the hygiene handler
|
|
@@ -769,6 +778,10 @@ export class SynaluxStorage extends SupabaseStorage {
|
|
|
769
778
|
project: "prism-mcp",
|
|
770
779
|
});
|
|
771
780
|
const inventory = result.inventory;
|
|
781
|
+
if (!inventory || !['ledger_entries', 'active_projects', 'ledger_missing_embeddings'].every(field => Number.isSafeInteger(inventory[field]) && inventory[field] >= 0)
|
|
782
|
+
|| inventory.ledger_missing_embeddings > inventory.ledger_entries) {
|
|
783
|
+
throw new Error('Health inventory unavailable');
|
|
784
|
+
}
|
|
772
785
|
const totalActiveEntries = typeof inventory?.ledger_entries === "number" ? inventory.ledger_entries : 0;
|
|
773
786
|
const totalHandoffs = typeof inventory?.active_projects === "number" ? inventory.active_projects : 0;
|
|
774
787
|
// Hardcoding 0 here certified a 100%-missing-embeddings outage as
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* ═══════════════════════════════════════════════════════════════════
|
|
17
17
|
*/
|
|
18
18
|
import { debugLog } from "../utils/logger.js";
|
|
19
|
+
const BACKFILL_SCOPE_MESSAGES = {
|
|
20
|
+
empty: '✅ No eligible entries with missing embeddings found in the requested scope.',
|
|
21
|
+
complete: 'All scanned entries now have embeddings for semantic search.',
|
|
22
|
+
};
|
|
19
23
|
import { activeStorageBackend, getStorage } from "../storage/index.js";
|
|
20
24
|
import { getEmbeddingProvider } from "../utils/llm/factory.js";
|
|
21
25
|
import { PRISM_USER_ID } from "../config.js";
|
|
@@ -78,6 +82,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
78
82
|
const params = {
|
|
79
83
|
"embedding": "is.null",
|
|
80
84
|
"archived_at": "is.null",
|
|
85
|
+
"deleted_at": "is.null",
|
|
81
86
|
user_id: `eq.${PRISM_USER_ID}`,
|
|
82
87
|
order: "id.asc",
|
|
83
88
|
limit: String(safeLimit),
|
|
@@ -95,7 +100,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
95
100
|
return {
|
|
96
101
|
content: [{
|
|
97
102
|
type: "text",
|
|
98
|
-
text:
|
|
103
|
+
text: BACKFILL_SCOPE_MESSAGES.empty,
|
|
99
104
|
}],
|
|
100
105
|
isError: false,
|
|
101
106
|
};
|
|
@@ -165,7 +170,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
165
170
|
`• Total scanned: ${entries.length}\n\n` +
|
|
166
171
|
(failed > 0
|
|
167
172
|
? `⚠️ ${failed} entries could not be repaired. Check server logs for details.`
|
|
168
|
-
:
|
|
173
|
+
: BACKFILL_SCOPE_MESSAGES.complete),
|
|
169
174
|
}],
|
|
170
175
|
isError: false,
|
|
171
176
|
_stats: { repaired, failed, last_id: entries[entries.length - 1]?.id },
|
|
@@ -810,6 +810,22 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
810
810
|
keywords,
|
|
811
811
|
role: effectiveRole, // v3.0: Hivemind role scoping (dashboard fallback)
|
|
812
812
|
});
|
|
813
|
+
// SQLite skips an identical (project, conversation_id, summary) saved in the
|
|
814
|
+
// last 5 minutes and returns { id, deduplicated: true } without writing.
|
|
815
|
+
// Nothing new was saved, so this is not a drift checkpoint and must not read
|
|
816
|
+
// as one: no timer reset, no "saved" line, no handoff nudge, and no second
|
|
817
|
+
// embedding or auto-link pass over the existing row.
|
|
818
|
+
if (result?.deduplicated === true) {
|
|
819
|
+
return {
|
|
820
|
+
content: [{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: (_saveLedgerGateWarning ? `⚠️ ${_saveLedgerGateWarning}\n\n` : "") +
|
|
823
|
+
`ℹ️ An identical ledger entry for project "${project}" was already saved in the last 5 minutes; nothing new was written.` +
|
|
824
|
+
resolverNote,
|
|
825
|
+
}],
|
|
826
|
+
isError: false,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
813
829
|
// ─── Fire-and-forget embedding generation ───
|
|
814
830
|
let embeddingQueued = false;
|
|
815
831
|
if (result) {
|
|
@@ -908,6 +924,14 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
908
924
|
debugLog(`[session_save_ledger] Background decay failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
909
925
|
});
|
|
910
926
|
const metricsBlock = formatInferenceMetrics();
|
|
927
|
+
// GATE 5: a ledger entry that was WRITTEN is a drift checkpoint. Reset the
|
|
928
|
+
// timer here, on the write path only. It used to be reset by the server
|
|
929
|
+
// after every returned result, so a context_not_loaded refusal or the
|
|
930
|
+
// greeting-only skip silenced the hourly reminder without saving anything.
|
|
931
|
+
{
|
|
932
|
+
const { noteDriftCheck } = await import("../session/sessionContext.js");
|
|
933
|
+
noteDriftCheck(conversation_id);
|
|
934
|
+
}
|
|
911
935
|
return {
|
|
912
936
|
content: [{
|
|
913
937
|
type: "text",
|
|
@@ -920,6 +944,9 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
920
944
|
(embeddingQueued
|
|
921
945
|
? `📊 Embedding generation queued for semantic search.`
|
|
922
946
|
: `📊 Primary history saved; optional semantic indexing was not queued.`) +
|
|
947
|
+
// The ledger logs what happened; the next session starts from the
|
|
948
|
+
// handoff. Only a written entry asks, never the skip or a refusal.
|
|
949
|
+
`\n💾 If this checkpoint ends a unit of work, also save the handoff with session_save_handoff.` +
|
|
923
950
|
metricsBlock +
|
|
924
951
|
resolverNote,
|
|
925
952
|
}],
|
package/docs/prism-browser.md
CHANGED
|
@@ -99,11 +99,13 @@ size alone. Emulate the device instead:
|
|
|
99
99
|
`viewport-fit=cover`; without it an iPhone 17 reports 0. Chromium applies
|
|
100
100
|
them either way, so check that your page sets it.
|
|
101
101
|
|
|
102
|
-
Flags given explicitly override the preset, so
|
|
103
|
-
`--device iphone-17 --viewport 874x402 --safe-area
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
still
|
|
102
|
+
Flags given explicitly override the preset, so
|
|
103
|
+
`--device iphone-17 --viewport 874x402 --safe-area T,R,B,L` is the same phone
|
|
104
|
+
turned sideways. Landscape insets were not measured and are not part of the
|
|
105
|
+
presets: put the values your device reports in place of `T,R,B,L`, because
|
|
106
|
+
without `--safe-area` the portrait insets still apply. The engine stays
|
|
107
|
+
Chromium, and the user agent stays the desktop one, so pages that branch on the
|
|
108
|
+
user-agent string still take their desktop path.
|
|
107
109
|
|
|
108
110
|
```bash
|
|
109
111
|
printf '%s\n' \
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "20.21.
|
|
3
|
+
"version": "20.21.21",
|
|
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",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"hono": "^4.13.5",
|
|
84
84
|
"ip-address": "^10.2.0",
|
|
85
85
|
"protobufjs": "^7.6.5",
|
|
86
|
-
"sharp": "^0.35.
|
|
86
|
+
"sharp": "^0.35.5",
|
|
87
87
|
"tar": "^7.5.19",
|
|
88
88
|
"nanoid": "^3.3.17"
|
|
89
89
|
},
|