negotium 0.3.0 → 0.3.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/dist/agent-helpers.js +107 -6
- package/dist/agent-helpers.js.map +11 -11
- package/dist/background-bash.js +3 -2
- package/dist/background-bash.js.map +4 -4
- package/dist/browser-runtime.js +3 -2
- package/dist/browser-runtime.js.map +4 -4
- package/dist/chunk-s2gez3wg.js.map +1 -1
- package/dist/{chunk-1y2xw0xe.js → chunk-vvfwqxnh.js} +33 -3
- package/dist/{chunk-1y2xw0xe.js.map → chunk-vvfwqxnh.js.map} +8 -8
- package/dist/hosted-agent.js +34 -4
- package/dist/hosted-agent.js.map +8 -8
- package/dist/main.js +1179 -366
- package/dist/main.js.map +24 -21
- package/dist/mcp-catalog.js +2 -1
- package/dist/mcp-catalog.js.map +3 -3
- package/dist/mcp-factories.js +635 -256
- package/dist/mcp-factories.js.map +15 -13
- package/dist/mcp-servers.js +3 -1
- package/dist/mcp-servers.js.map +3 -3
- package/dist/prompts.js +8 -2
- package/dist/prompts.js.map +5 -5
- package/dist/query-runtime.js +3 -2
- package/dist/query-runtime.js.map +4 -4
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/mcp/canonical-bridge-config.ts +1 -1
- package/dist/runtime/src/mcp/canonical-proxy-server.ts +74 -1
- package/dist/runtime/src/mcp/decision-server.ts +21 -0
- package/dist/runtime/src/mcp/factories/decision.ts +178 -0
- package/dist/runtime/src/mcp/factories/index.ts +6 -0
- package/dist/runtime/src/mcp/runtime-spec.ts +1 -0
- package/dist/runtime/src/node-host.ts +1 -0
- package/dist/runtime/src/platform/config.ts +2 -1
- package/dist/runtime/src/platform/mcp-catalog-policy.ts +1 -0
- package/dist/runtime/src/platform/mcp-config.ts +31 -1
- package/dist/runtime/src/prompts/builders.ts +5 -0
- package/dist/runtime/src/runtime/visual-html.ts +68 -2
- package/dist/runtime/src/storage/decisions.ts +231 -0
- package/dist/runtime/src/storage/storage-public.ts +2 -0
- package/dist/runtime/src/types.ts +13 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +71 -4
- package/dist/runtime-helpers.js.map +5 -5
- package/dist/storage.js +261 -56
- package/dist/storage.js.map +5 -4
- package/dist/types/apps/negotium/src/mcp-servers.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/canonical-bridge-config.d.ts +1 -1
- package/dist/types/packages/core/src/mcp/factories/decision.d.ts +16 -0
- package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -0
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +1 -1
- package/dist/types/packages/core/src/platform/config.d.ts +2 -1
- package/dist/types/packages/core/src/platform/mcp-catalog-policy.d.ts +4 -0
- package/dist/types/packages/core/src/storage/decisions.d.ts +45 -0
- package/dist/types/packages/core/src/storage/storage-public.d.ts +2 -0
- package/dist/types/packages/core/src/types.d.ts +12 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +3 -2
- package/dist/vault.js.map +4 -4
- package/install-browser-rs.mjs +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { logger } from "#platform/logger";
|
|
4
|
+
import { sanitizeFileName } from "#security/sanitize";
|
|
5
|
+
import { resolveStorageDataDir } from "#storage/storage-host";
|
|
6
|
+
import type { AgentKind, DecisionSnapshot } from "#types";
|
|
7
|
+
|
|
8
|
+
export type StoredDecision = DecisionSnapshot;
|
|
9
|
+
|
|
10
|
+
export const DECISION_STATUS_VALUES = [
|
|
11
|
+
"proposed",
|
|
12
|
+
"accepted",
|
|
13
|
+
"executed",
|
|
14
|
+
"rejected",
|
|
15
|
+
"superseded",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
interface DecisionFileShape {
|
|
19
|
+
version: 1;
|
|
20
|
+
decisions: StoredDecision[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function safeDecisionScopeKey(scopeKey: string): string {
|
|
24
|
+
const safe = sanitizeFileName(scopeKey);
|
|
25
|
+
if (!safe || safe === "." || safe === "..") {
|
|
26
|
+
throw new Error(`decisions: refusing unsafe scope key: ${scopeKey}`);
|
|
27
|
+
}
|
|
28
|
+
return safe;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function decisionScopeKey(opts: { topicId?: string; session: string }): string {
|
|
32
|
+
return opts.topicId?.trim() || opts.session || "default";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getDecisionFilePath(userId: number | string, scopeKey: string): string {
|
|
36
|
+
void userId;
|
|
37
|
+
return join(resolveStorageDataDir(), "decisions", `${safeDecisionScopeKey(scopeKey)}.json`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getDecisionGraphSvgPath(userId: number | string, scopeKey: string): string {
|
|
41
|
+
void userId;
|
|
42
|
+
return join(
|
|
43
|
+
resolveStorageDataDir(),
|
|
44
|
+
"decision-renders",
|
|
45
|
+
safeDecisionScopeKey(scopeKey),
|
|
46
|
+
"latest.svg",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeDecisionGraphSvg(
|
|
51
|
+
userId: number | string,
|
|
52
|
+
scopeKey: string,
|
|
53
|
+
svg: string,
|
|
54
|
+
): string {
|
|
55
|
+
const path = getDecisionGraphSvgPath(userId, scopeKey);
|
|
56
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
57
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
58
|
+
writeFileSync(tmp, svg, "utf-8");
|
|
59
|
+
renameSync(tmp, path);
|
|
60
|
+
return path;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function readDecisions(userId: number | string, scopeKey: string): StoredDecision[] {
|
|
64
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
65
|
+
if (!existsSync(path)) return [];
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8")) as DecisionFileShape;
|
|
68
|
+
return Array.isArray(parsed?.decisions) ? parsed.decisions : [];
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.warn({ err: error, path }, "decisions: failed to read decision store");
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function writeDecisions(
|
|
76
|
+
userId: number | string,
|
|
77
|
+
scopeKey: string,
|
|
78
|
+
decisions: StoredDecision[],
|
|
79
|
+
): void {
|
|
80
|
+
const path = getDecisionFilePath(userId, scopeKey);
|
|
81
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
82
|
+
const payload: DecisionFileShape = { version: 1, decisions };
|
|
83
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
84
|
+
writeFileSync(tmp, JSON.stringify(payload, null, 2), "utf-8");
|
|
85
|
+
renameSync(tmp, path);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface DecisionCreateInput {
|
|
89
|
+
action: string;
|
|
90
|
+
reasoning: string;
|
|
91
|
+
agent: AgentKind;
|
|
92
|
+
model?: string;
|
|
93
|
+
status?: StoredDecision["status"];
|
|
94
|
+
causedBy?: string[];
|
|
95
|
+
timestamp?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DecisionUpdateInput {
|
|
99
|
+
id: string;
|
|
100
|
+
action?: string;
|
|
101
|
+
reasoning?: string;
|
|
102
|
+
status?: StoredDecision["status"];
|
|
103
|
+
causedBy?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function nextDecisionId(decisions: StoredDecision[]): number {
|
|
107
|
+
let max = 0;
|
|
108
|
+
for (const decision of decisions) {
|
|
109
|
+
const id = Number(decision.id);
|
|
110
|
+
if (Number.isInteger(id) && id > max) max = id;
|
|
111
|
+
}
|
|
112
|
+
return max + 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizedIds(ids: string[] | undefined): string[] | undefined {
|
|
116
|
+
if (!ids) return undefined;
|
|
117
|
+
const unique = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
118
|
+
return unique.length > 0 ? unique : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function validateDecisionGraph(decisions: StoredDecision[]): void {
|
|
122
|
+
const ids = new Set(decisions.map((decision) => decision.id));
|
|
123
|
+
for (const decision of decisions) {
|
|
124
|
+
for (const upstream of decision.causedBy ?? []) {
|
|
125
|
+
if (!ids.has(upstream)) {
|
|
126
|
+
throw new Error(`Decision #${decision.id} references missing decision #${upstream}.`);
|
|
127
|
+
}
|
|
128
|
+
if (upstream === decision.id) {
|
|
129
|
+
throw new Error(`Decision #${decision.id} cannot cause itself.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const visiting = new Set<string>();
|
|
135
|
+
const visited = new Set<string>();
|
|
136
|
+
const byId = new Map(decisions.map((decision) => [decision.id, decision]));
|
|
137
|
+
const visit = (id: string): void => {
|
|
138
|
+
if (visited.has(id)) return;
|
|
139
|
+
if (visiting.has(id)) throw new Error(`Decision graph contains a cycle at #${id}.`);
|
|
140
|
+
visiting.add(id);
|
|
141
|
+
for (const upstream of byId.get(id)?.causedBy ?? []) visit(upstream);
|
|
142
|
+
visiting.delete(id);
|
|
143
|
+
visited.add(id);
|
|
144
|
+
};
|
|
145
|
+
for (const id of ids) visit(id);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function createDecisions(
|
|
149
|
+
decisions: StoredDecision[],
|
|
150
|
+
inputs: DecisionCreateInput[],
|
|
151
|
+
): { decisions: StoredDecision[]; created: StoredDecision[] } {
|
|
152
|
+
const out = [...decisions];
|
|
153
|
+
const created: StoredDecision[] = [];
|
|
154
|
+
let id = nextDecisionId(out);
|
|
155
|
+
for (const input of inputs) {
|
|
156
|
+
const decision: StoredDecision = {
|
|
157
|
+
id: String(id++),
|
|
158
|
+
action: input.action.trim(),
|
|
159
|
+
reasoning: input.reasoning.trim(),
|
|
160
|
+
agent: input.agent,
|
|
161
|
+
status: input.status ?? "accepted",
|
|
162
|
+
timestamp: input.timestamp ?? Date.now(),
|
|
163
|
+
...(input.model ? { model: input.model } : {}),
|
|
164
|
+
...(normalizedIds(input.causedBy) ? { causedBy: normalizedIds(input.causedBy) } : {}),
|
|
165
|
+
};
|
|
166
|
+
out.push(decision);
|
|
167
|
+
created.push(decision);
|
|
168
|
+
}
|
|
169
|
+
validateDecisionGraph(out);
|
|
170
|
+
return { decisions: out, created };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function updateDecisions(
|
|
174
|
+
decisions: StoredDecision[],
|
|
175
|
+
updates: DecisionUpdateInput[],
|
|
176
|
+
): { decisions: StoredDecision[]; missing: string[] } {
|
|
177
|
+
const out = decisions.map((decision) => ({
|
|
178
|
+
...decision,
|
|
179
|
+
...(decision.causedBy ? { causedBy: [...decision.causedBy] } : {}),
|
|
180
|
+
}));
|
|
181
|
+
const byId = new Map(out.map((decision) => [decision.id, decision]));
|
|
182
|
+
const missing: string[] = [];
|
|
183
|
+
for (const update of updates) {
|
|
184
|
+
const decision = byId.get(update.id);
|
|
185
|
+
if (!decision) {
|
|
186
|
+
missing.push(update.id);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (update.action !== undefined) decision.action = update.action.trim();
|
|
190
|
+
if (update.reasoning !== undefined) decision.reasoning = update.reasoning.trim();
|
|
191
|
+
if (update.status !== undefined) decision.status = update.status;
|
|
192
|
+
if (update.causedBy !== undefined) {
|
|
193
|
+
const ids = normalizedIds(update.causedBy);
|
|
194
|
+
if (ids) decision.causedBy = ids;
|
|
195
|
+
else delete decision.causedBy;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
validateDecisionGraph(out);
|
|
199
|
+
return { decisions: out, missing };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function deleteDecisions(
|
|
203
|
+
decisions: StoredDecision[],
|
|
204
|
+
opts: { ids?: string[]; all?: boolean },
|
|
205
|
+
): { decisions: StoredDecision[]; removed: number } {
|
|
206
|
+
if (opts.all) return { decisions: [], removed: decisions.length };
|
|
207
|
+
const ids = new Set(opts.ids ?? []);
|
|
208
|
+
const kept = decisions
|
|
209
|
+
.filter((decision) => !ids.has(decision.id))
|
|
210
|
+
.map((decision) => {
|
|
211
|
+
const causedBy = decision.causedBy?.filter((id) => !ids.has(id));
|
|
212
|
+
const next = { ...decision };
|
|
213
|
+
if (causedBy && causedBy.length > 0) next.causedBy = causedBy;
|
|
214
|
+
else delete next.causedBy;
|
|
215
|
+
return next;
|
|
216
|
+
});
|
|
217
|
+
return { decisions: kept, removed: decisions.length - kept.length };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function renderDecisionList(decisions: StoredDecision[]): string {
|
|
221
|
+
if (decisions.length === 0) return "Decisions (0 recorded)";
|
|
222
|
+
return [
|
|
223
|
+
`Decisions (${decisions.length} recorded)`,
|
|
224
|
+
...decisions.map((decision) => {
|
|
225
|
+
const causes = decision.causedBy?.length
|
|
226
|
+
? ` <- ${decision.causedBy.map((id) => `#${id}`).join(", ")}`
|
|
227
|
+
: "";
|
|
228
|
+
return `[${decision.status}] #${decision.id} ${decision.action}${causes}\n ${decision.reasoning}`;
|
|
229
|
+
}),
|
|
230
|
+
].join("\n");
|
|
231
|
+
}
|
|
@@ -20,6 +20,8 @@ export * as askUserGates from "#storage/ask-user-gates";
|
|
|
20
20
|
export * from "#storage/ask-user-gates";
|
|
21
21
|
export * as conversations from "#storage/conversations";
|
|
22
22
|
export * from "#storage/conversations";
|
|
23
|
+
export * as decisions from "#storage/decisions";
|
|
24
|
+
export * from "#storage/decisions";
|
|
23
25
|
export type {
|
|
24
26
|
ForumTopicInfo,
|
|
25
27
|
TopicRow,
|
|
@@ -97,6 +97,19 @@ export interface TaskSnapshot {
|
|
|
97
97
|
owner?: string;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/** One topic-scoped decision and its incoming causal edges. */
|
|
101
|
+
export interface DecisionSnapshot {
|
|
102
|
+
id: string;
|
|
103
|
+
action: string;
|
|
104
|
+
reasoning: string;
|
|
105
|
+
agent: AgentKind;
|
|
106
|
+
model?: string;
|
|
107
|
+
status: "proposed" | "accepted" | "executed" | "rejected" | "superseded";
|
|
108
|
+
/** Upstream decision ids. Each entry forms a directed upstream -> this edge. */
|
|
109
|
+
causedBy?: string[];
|
|
110
|
+
timestamp: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
export type UnifiedEvent =
|
|
101
114
|
| {
|
|
102
115
|
type: "user_message";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.3.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.3.2";
|
package/dist/runtime-helpers.js
CHANGED
|
@@ -535,7 +535,7 @@ var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
|
|
|
535
535
|
var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
536
536
|
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
537
537
|
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
538
|
-
var BROWSER_RS_VERSION = "v0.1.
|
|
538
|
+
var BROWSER_RS_VERSION = "v0.1.21";
|
|
539
539
|
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
540
540
|
function versionAtLeast(actualVersion, minimumVersion) {
|
|
541
541
|
const actual = actualVersion.split(".").map(Number);
|
|
@@ -605,6 +605,7 @@ var TSX_LOADER = createRequire(import.meta.url).resolve("tsx");
|
|
|
605
605
|
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
606
606
|
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
607
607
|
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
608
|
+
var DECISION_SERVER = resolve(PROJECT_ROOT, "src/mcp/decision-server.ts");
|
|
608
609
|
var BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
|
|
609
610
|
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
610
611
|
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
@@ -1439,6 +1440,10 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
1439
1440
|
.controls button:focus-visible{outline:2px solid var(--celadon);outline-offset:1px}
|
|
1440
1441
|
.zoom-value{min-width:46px;color:var(--graphite);font:500 11px/1 Geist,system-ui,sans-serif;text-align:center;font-variant-numeric:tabular-nums}
|
|
1441
1442
|
.error{margin:0;white-space:pre-wrap;color:#7D2E2E;background:#FFF5F2;border:1px solid #E4B9B1;border-radius:6px;padding:14px;font:13px ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
1443
|
+
.failure{max-width:56ch;margin:0 auto}
|
|
1444
|
+
.failure p{margin:0 0 12px;color:var(--graphite);font:14px/1.6 Geist,system-ui,sans-serif}
|
|
1445
|
+
.failure details{color:var(--graphite);font:12px/1.5 Geist,system-ui,sans-serif}
|
|
1446
|
+
.failure summary{cursor:pointer;padding:4px 0}
|
|
1442
1447
|
@media(max-width:600px){.viewport{padding:54px 14px 18px}.controls{top:10px;right:10px}}
|
|
1443
1448
|
</style>
|
|
1444
1449
|
</head>
|
|
@@ -1453,11 +1458,58 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
1453
1458
|
<script data-otium-mermaid-runtime src="${safeScriptUrl}"></script>
|
|
1454
1459
|
<script>
|
|
1455
1460
|
(async () => {
|
|
1461
|
+
// An unrendered document reports itself in more than one voice: Mermaid's
|
|
1462
|
+
// own 0x0 guard, and the browser refusing geometry on a path that was
|
|
1463
|
+
// never laid out. Both mean the same thing, so both are worth one retry
|
|
1464
|
+
// and, if it still fails, the same explanation.
|
|
1465
|
+
const unrendered = (error) => {
|
|
1466
|
+
const message = String(error && error.message ? error.message : error);
|
|
1467
|
+
return message.indexOf("not in render tree") !== -1 || message.indexOf("path is empty") !== -1;
|
|
1468
|
+
};
|
|
1456
1469
|
try {
|
|
1457
1470
|
const runtime = globalThis.mermaid;
|
|
1458
1471
|
if (!runtime) throw new Error("Mermaid renderer failed to load.");
|
|
1459
1472
|
runtime.initialize({ startOnLoad: false, securityLevel: "strict", theme: ${safeTheme} });
|
|
1460
|
-
|
|
1473
|
+
const host = document.querySelector(".mermaid");
|
|
1474
|
+
// Mermaid sizes every label by appending a probe <svg> to the body and
|
|
1475
|
+
// reading getBBox(), and it throws "svg element not in render tree" the
|
|
1476
|
+
// moment that comes back 0x0. That is what a hidden panel looks like from
|
|
1477
|
+
// in here: the document exists but nothing is in the render tree, so the
|
|
1478
|
+
// measurement has no geometry to report. Ask the same question Mermaid
|
|
1479
|
+
// will ask, and only start once it has an answer.
|
|
1480
|
+
const measurable = () => {
|
|
1481
|
+
const probe = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1482
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
1483
|
+
text.textContent = "M";
|
|
1484
|
+
probe.appendChild(text);
|
|
1485
|
+
document.body.appendChild(probe);
|
|
1486
|
+
let box = { width: 0, height: 0 };
|
|
1487
|
+
try { box = text.getBBox(); } catch (ignored) {}
|
|
1488
|
+
probe.remove();
|
|
1489
|
+
return box.width > 0 || box.height > 0;
|
|
1490
|
+
};
|
|
1491
|
+
// requestAnimationFrame is the right clock here: a hidden document stops
|
|
1492
|
+
// being animated, so this waits without spinning and resumes on the frame
|
|
1493
|
+
// the panel is shown. The cap counts rendered frames, not wall time.
|
|
1494
|
+
const waitUntilMeasurable = async (maxFrames) => {
|
|
1495
|
+
for (let frame = 0; frame < maxFrames; frame += 1) {
|
|
1496
|
+
if (measurable()) return true;
|
|
1497
|
+
await new Promise((next) => requestAnimationFrame(next));
|
|
1498
|
+
}
|
|
1499
|
+
return measurable();
|
|
1500
|
+
};
|
|
1501
|
+
await waitUntilMeasurable(600);
|
|
1502
|
+
try {
|
|
1503
|
+
await runtime.run({ querySelector: ".mermaid" });
|
|
1504
|
+
} catch (firstAttempt) {
|
|
1505
|
+
if (!unrendered(firstAttempt)) throw firstAttempt;
|
|
1506
|
+
// The panel can be hidden again between the probe and the real measure.
|
|
1507
|
+
// Clear the marker Mermaid leaves behind so the retry is not skipped as
|
|
1508
|
+
// already done, then wait for the render tree once more.
|
|
1509
|
+
host.removeAttribute("data-processed");
|
|
1510
|
+
await waitUntilMeasurable(600);
|
|
1511
|
+
await runtime.run({ querySelector: ".mermaid" });
|
|
1512
|
+
}
|
|
1461
1513
|
const viewport = document.querySelector(".viewport");
|
|
1462
1514
|
const svg = document.querySelector(".mermaid svg");
|
|
1463
1515
|
const value = document.querySelector(".zoom-value");
|
|
@@ -1484,7 +1536,22 @@ function buildMermaidHtml(code, theme, scriptUrl = MERMAID_BROWSER_ASSET_RELATIV
|
|
|
1484
1536
|
applyScale(scale, false);
|
|
1485
1537
|
} catch (error) {
|
|
1486
1538
|
document.querySelector(".controls")?.remove();
|
|
1487
|
-
|
|
1539
|
+
const raw = String(error && error.message ? error.message : error);
|
|
1540
|
+
const escape = (value) => value.replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]));
|
|
1541
|
+
// Mermaid aborts the whole render when it cannot place an edge label:
|
|
1542
|
+
// cardinality markers ask for a point a fixed distance along the edge,
|
|
1543
|
+
// and a relation laid out shorter than that walks off the end. Nothing
|
|
1544
|
+
// is wrong with the diagram, so the raw message sends authors looking
|
|
1545
|
+
// for a syntax error that does not exist. Say what actually moves it.
|
|
1546
|
+
const note = raw.indexOf("Could not find a suitable point") !== -1
|
|
1547
|
+
? "Two nodes ended up too close together for Mermaid to fit a label on the edge between them. Renaming a node, adding another, or setting an explicit direction usually spreads the layout enough to render."
|
|
1548
|
+
: unrendered(error)
|
|
1549
|
+
? "The panel stayed hidden long enough that there was never a laid-out page to measure the diagram against. Reopening the panel renders it."
|
|
1550
|
+
: "This diagram could not be rendered.";
|
|
1551
|
+
document.querySelector(".viewport").innerHTML =
|
|
1552
|
+
'<div class="failure"><p>' + escape(note) +
|
|
1553
|
+
'</p><details><summary>Technical detail</summary><pre class="error">' +
|
|
1554
|
+
escape(raw) + '</pre></details></div>';
|
|
1488
1555
|
}
|
|
1489
1556
|
})();
|
|
1490
1557
|
</script>
|
|
@@ -1596,4 +1663,4 @@ export {
|
|
|
1596
1663
|
CLAUDE_EFFORT_VALUES
|
|
1597
1664
|
};
|
|
1598
1665
|
|
|
1599
|
-
//# debugId=
|
|
1666
|
+
//# debugId=002587304D7533BD64756E2164756E21
|