beatrina 0.8.6

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.
Files changed (114) hide show
  1. package/LICENSE +92 -0
  2. package/NOTICES +72 -0
  3. package/README.md +124 -0
  4. package/bin/beatrina.mjs +223 -0
  5. package/bin/cli.mjs +80 -0
  6. package/bin/failsafe.mjs +74 -0
  7. package/bin/identity.mjs +45 -0
  8. package/bin/prova-post.mjs +51 -0
  9. package/bin/sessions.mjs +95 -0
  10. package/bin/shortcut.mjs +151 -0
  11. package/bin/update-check.mjs +55 -0
  12. package/bin/upgrade.mjs +76 -0
  13. package/build-info.json +1 -0
  14. package/carmar_V0.8.6.html +1310 -0
  15. package/check/acceptance.mjs +278 -0
  16. package/check/session.mjs +215 -0
  17. package/engines/js/document-exec.mjs +82 -0
  18. package/engines/js/persist.mjs +214 -0
  19. package/engines/js/worker.mjs +424 -0
  20. package/engines/python/adapter.py +577 -0
  21. package/engines/python/analyze.py +814 -0
  22. package/engines/python/bootstrap.py +309 -0
  23. package/engines/python/dataview.py +735 -0
  24. package/engines/python/debugger.py +346 -0
  25. package/engines/python/document_exec.py +158 -0
  26. package/engines/python/engine.json +28 -0
  27. package/engines/python/handoff.py +118 -0
  28. package/engines/python/worker.py +564 -0
  29. package/engines/r/engine.json +25 -0
  30. package/engines/r/handoff.R +92 -0
  31. package/failsafe/ai-policy.R +255 -0
  32. package/failsafe/ai-store.R +373 -0
  33. package/failsafe/cite.R +418 -0
  34. package/failsafe/journal.R +684 -0
  35. package/failsafe/plugins.R +809 -0
  36. package/failsafe/serve.R +5500 -0
  37. package/host/ai-policy.mjs +218 -0
  38. package/host/deployment.mjs +160 -0
  39. package/host/engine-js.mjs +98 -0
  40. package/host/engine-pool.mjs +383 -0
  41. package/host/engine-python.mjs +228 -0
  42. package/host/engine-r.mjs +206 -0
  43. package/host/engine-stdio.mjs +401 -0
  44. package/host/journal-store.mjs +749 -0
  45. package/host/main.mjs +503 -0
  46. package/host/planes/README.md +41 -0
  47. package/host/planes/ai-store.mjs +327 -0
  48. package/host/planes/ai.mjs +467 -0
  49. package/host/planes/analyze.mjs +397 -0
  50. package/host/planes/cite.mjs +517 -0
  51. package/host/planes/files.mjs +0 -0
  52. package/host/planes/jobs.mjs +704 -0
  53. package/host/planes/journal.mjs +53 -0
  54. package/host/planes/latex.mjs +201 -0
  55. package/host/planes/mcp.mjs +493 -0
  56. package/host/planes/pair.mjs +325 -0
  57. package/host/planes/pipe-term.mjs +122 -0
  58. package/host/planes/plugins.mjs +112 -0
  59. package/host/planes/proc-tree.mjs +76 -0
  60. package/host/planes/sessions.mjs +434 -0
  61. package/host/planes/settings.mjs +164 -0
  62. package/host/planes/terminal.mjs +286 -0
  63. package/host/planes/test-file.mjs +80 -0
  64. package/host/planes/update.mjs +214 -0
  65. package/host/plugin-store.mjs +838 -0
  66. package/host/server.mjs +441 -0
  67. package/host/settings.mjs +379 -0
  68. package/host/update-record.mjs +59 -0
  69. package/host/user-dirs.mjs +117 -0
  70. package/host/windows-runtime.mjs +145 -0
  71. package/host/worker-plane.mjs +713 -0
  72. package/host/ws.mjs +190 -0
  73. package/kernel/analyze.R +668 -0
  74. package/kernel/deployment.R +165 -0
  75. package/kernel/examples/NOTICE.md +38 -0
  76. package/kernel/examples/tna-complete-tutorial.Rmd +210 -0
  77. package/kernel/fileio.R +656 -0
  78. package/kernel/index.html +96 -0
  79. package/kernel/job-run.R +391 -0
  80. package/kernel/jobs.R +276 -0
  81. package/kernel/kernel-protocol +1 -0
  82. package/kernel/kernel-version +1 -0
  83. package/kernel/kernel.R +671 -0
  84. package/kernel/knitr-run.R +245 -0
  85. package/kernel/latex.R +609 -0
  86. package/kernel/mcp/carmar-mcp.mjs +516 -0
  87. package/kernel/notebook-page.R +67 -0
  88. package/kernel/plugins/csl/apa/apa.csl +2273 -0
  89. package/kernel/plugins/csl/apa/plugin.json +19 -0
  90. package/kernel/plugins/csl/chicago-author-date/chicago-author-date.csl +4216 -0
  91. package/kernel/plugins/csl/chicago-author-date/plugin.json +19 -0
  92. package/kernel/plugins/csl/harvard-cite-them-right/harvard-cite-them-right.csl +316 -0
  93. package/kernel/plugins/csl/harvard-cite-them-right/plugin.json +19 -0
  94. package/kernel/plugins/csl/ieee/ieee.csl +519 -0
  95. package/kernel/plugins/csl/ieee/plugin.json +19 -0
  96. package/kernel/plugins/csl/modern-language-association/modern-language-association.csl +1184 -0
  97. package/kernel/plugins/csl/modern-language-association/plugin.json +19 -0
  98. package/kernel/plugins/csl/nature/nature.csl +189 -0
  99. package/kernel/plugins/csl/nature/plugin.json +19 -0
  100. package/kernel/plugins/latex/apa7/apa7.json +14 -0
  101. package/kernel/plugins/latex/apa7/plugin.json +19 -0
  102. package/kernel/plugins/latex/elsarticle/elsarticle.json +14 -0
  103. package/kernel/plugins/latex/elsarticle/plugin.json +19 -0
  104. package/kernel/plugins/latex/ieeetran/ieeetran.json +10 -0
  105. package/kernel/plugins/latex/ieeetran/plugin.json +19 -0
  106. package/kernel/project.R +131 -0
  107. package/kernel/settings.R +410 -0
  108. package/kernel/sniff.R +769 -0
  109. package/kernel/worker-boot.R +22 -0
  110. package/kernel/worker.R +3496 -0
  111. package/lib/agent-authoring-contract.js +547 -0
  112. package/lib/cell-kinds.js +108 -0
  113. package/lib/engine-labels.js +324 -0
  114. package/package.json +32 -0
@@ -0,0 +1,53 @@
1
+ // planes/journal.mjs — the FOURTH allow-list: the durable document journal.
2
+ //
3
+ // Answered by the HOST for the reason the file ops are (a kilobyte append is
4
+ // not evaluation and must never queue behind a running cell — serve.R measured
5
+ // 5 ms while a chunk sleeps), and page-only in BOTH senses:
6
+ //
7
+ // · AGENT_REFUSED, because the journal holds every keystroke, INCLUDING
8
+ // deleted text — it is the user's, never an agent's to read or write;
9
+ // · PAGE_ONLY_CLASSES, because AGENT_REFUSED alone is an honour system: a
10
+ // client that simply never declared itself would sail through it, which is
11
+ // exactly the hole `ai-key` had until 0.60.89.
12
+ //
13
+ // The store is host/journal-store.mjs; this file is only the wire. The one rule
14
+ // that looks like plumbing and is not: the reply is written by journalFrame(),
15
+ // which splices each record's BYTES into the JSON by hand. Handing the parsed
16
+ // records back to a serialiser is relay_frame's lesson one door over.
17
+
18
+ import { createJournalStore, journalFrame } from "../journal-store.mjs";
19
+ import { PAGE_ONLY_CLASSES } from "../server.mjs";
20
+
21
+ export const JOURNAL_OPS = Object.freeze([
22
+ "journal_open", "journal_append", "journal_checkpoint", "journal_load",
23
+ "journal_read", "journal_versions", "journal_pin", "journal_unpin",
24
+ "journal_pin_read", "journal_list", "journal_delete", "journal_export",
25
+ "journal_archive", "journal_rename", "journal_probe",
26
+ ]);
27
+
28
+ export function createPlane({ audit } = {}) {
29
+ const store = createJournalStore();
30
+ return {
31
+ name: "journal",
32
+ audits: true, // the audit line carries the document key; see server.mjs
33
+ ops: JOURNAL_OPS,
34
+ agentRefused: JOURNAL_OPS,
35
+ commands: JOURNAL_OPS,
36
+ capabilities: ["document-journal-v1"],
37
+ store,
38
+ handle(cmd, rec, ctx) {
39
+ if (typeof cmd.id !== "string" || !cmd.id.length) return undefined;
40
+ // Class, not role: what the upgrade gate PROVED about the connection,
41
+ // which nothing the client says afterwards can change.
42
+ if (!PAGE_ONLY_CLASSES.includes(rec.class)) {
43
+ audit?.("journal-refused", { detail: cmd.type, class: rec.class || "unknown" });
44
+ rec.ws.send(ctx.enc({ type: cmd.type, id: cmd.id, code: "refused",
45
+ error: "The document journal is available to the notebook page only." }));
46
+ return undefined;
47
+ }
48
+ audit?.(cmd.type, { id: cmd.id, key: typeof cmd.key === "string" ? cmd.key : "" });
49
+ rec.ws.send(journalFrame(store.answerGuarded(cmd)));
50
+ return undefined;
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,201 @@
1
+ // latex.mjs — the LaTeX live preview: pandoc over the root .tex, its includes
2
+ // pulled in, its figures inlined. A port of spike/latex.R `latex_preview()`
3
+ // and serve.R's `latex_preview` handler, rule for rule.
4
+ //
5
+ // Three rules carried over, because each is a security or honesty decision:
6
+ // - No path travels on the wire in either direction. Includes resolve
7
+ // against the ROOT's own directory whatever a buffer is called, and
8
+ // figures come back as data: URIs the host built from files it resolved.
9
+ // - Everything is BOUNDED: source, output, one figure, all figures, files
10
+ // followed, include depth. A cycle costs a stack frame, never the session.
11
+ // - A pandoc parse failure is `partial`, not an error: an unclosed group is
12
+ // the ordinary state of a document being typed, and the page keeps the
13
+ // last good render behind the note.
14
+ //
15
+ // Page-only in both senses (role AND class), like serve.R.
16
+
17
+ import fs from "node:fs";
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+ import crypto from "node:crypto";
21
+ import { findPandoc, run } from "./cite.mjs";
22
+
23
+ export const LATEX_PREVIEW_TIMEOUT_MS = 8000;
24
+ export const LATEX_PREVIEW_MAX_SOURCE = 2e6;
25
+ export const LATEX_PREVIEW_MAX_HTML = 6e6;
26
+ export const LATEX_PREVIEW_MAX_IMAGE = 1.5e6;
27
+ export const LATEX_PREVIEW_MAX_ASSETS = 8e6;
28
+ export const LATEX_PREVIEW_MAX_FILES = 40;
29
+ export const LATEX_PREVIEW_DEPTH = 6;
30
+ export const LATEX_PREVIEW_IMAGE_TYPES = Object.freeze({
31
+ png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
32
+ gif: "image/gif", svg: "image/svg+xml", webp: "image/webp",
33
+ });
34
+
35
+ const OPS = ["latex_preview"];
36
+ const AGENT_WHY = { latex_preview: "Agents cannot render a LaTeX preview; read the .tex instead." };
37
+ const PAGE_ONLY_CLASSES = ["served", "file", "local"];
38
+ const scalar = (x) => typeof x === "string";
39
+ const bytes = (s) => Buffer.byteLength(s, "utf8");
40
+
41
+ /**
42
+ * Is `p` inside `dir`, both canonicalised? The trailing separator matters:
43
+ * without it /p/figs would admit /p/figs-private.
44
+ * @returns {string} the resolved path, or "" when outside or absent.
45
+ */
46
+ export function latexWithin(dir, p) {
47
+ if (!scalar(dir) || !dir || !scalar(p) || !p) return "";
48
+ let root;
49
+ try { if (!fs.statSync(dir).isDirectory()) return ""; root = fs.realpathSync(dir); } catch { return ""; }
50
+ const full0 = /^([A-Za-z]:)?[/\\]/.test(p) ? p : path.join(root, p);
51
+ let full;
52
+ try { full = fs.realpathSync(full0); } catch { return ""; }
53
+ if (full !== root && !full.startsWith(root + path.sep)) return "";
54
+ return full;
55
+ }
56
+
57
+ /** Pull \input/\include/\subfile bodies into one source. Buffers win over disk. */
58
+ export function latexPreviewExpand(source, dir, buffers = {}, seen = [], depth = LATEX_PREVIEW_DEPTH) {
59
+ if (depth <= 0) return source;
60
+ const pattern = /\\(?:input|include|subfile)\{([^{}]{1,200})\}/g;
61
+ let out = "";
62
+ let at = 0;
63
+ let m;
64
+ let any = false;
65
+ while ((m = pattern.exec(source)) !== null) {
66
+ any = true;
67
+ let rel = m[1];
68
+ if (!/\.[A-Za-z0-9]{1,5}$/.test(rel)) rel += ".tex";
69
+ let body = null;
70
+ if (!seen.includes(rel) && seen.length < LATEX_PREVIEW_MAX_FILES) {
71
+ if (Object.hasOwn(buffers, rel) && typeof buffers[rel] === "string") body = buffers[rel];
72
+ else {
73
+ const hit = latexWithin(dir, rel);
74
+ if (hit) {
75
+ try { if (fs.statSync(hit).size <= LATEX_PREVIEW_MAX_SOURCE) body = fs.readFileSync(hit, "utf8").replace(/\r?\n$/, ""); } catch { body = null; }
76
+ }
77
+ }
78
+ }
79
+ out += source.slice(at, m.index) + (body === null ? "" : latexPreviewExpand(body, dir, buffers, [...seen, rel], depth - 1));
80
+ at = m.index + m[0].length;
81
+ }
82
+ return any ? out + source.slice(at) : source;
83
+ }
84
+
85
+ /** The .bib files a source names, resolved and bounded. */
86
+ export function latexPreviewBibliography(source, dir) {
87
+ const named = [];
88
+ for (const m of source.matchAll(/\\(?:bibliography|addbibresource)(?:\[[^\]]*\])?\{([^{}]{1,300})\}/g)) {
89
+ m[1].split(",").map((s) => s.trim()).filter(Boolean).forEach((n) => named.push(/\.[A-Za-z0-9]{1,5}$/.test(n) ? n : `${n}.bib`));
90
+ }
91
+ return [...new Set(named)].map((n) => latexWithin(dir, n)).filter(Boolean);
92
+ }
93
+
94
+ const extOf = (s) => { const m = s.match(/\.([A-Za-z0-9]+)$/); return m ? m[1].toLowerCase() : null; };
95
+
96
+ /** Turn every local <img src> into a data: URI, within budget. */
97
+ export function latexPreviewInline(html, dir) {
98
+ let inlined = 0;
99
+ let budget = LATEX_PREVIEW_MAX_ASSETS;
100
+ const dropped = [];
101
+ const out = html.replace(/src="([^"]{1,400})"/g, (whole, src) => {
102
+ if (/^(?:data:|https?:|\/\/)/.test(src)) return whole;
103
+ const ext = extOf(src);
104
+ let mime = ext ? LATEX_PREVIEW_IMAGE_TYPES[ext] : undefined;
105
+ const candidates = !ext || !mime
106
+ ? Object.keys(LATEX_PREVIEW_IMAGE_TYPES).map((t) => `${src.replace(/\.[A-Za-z0-9]+$/, "")}.${t}`)
107
+ : [src];
108
+ let hit = "";
109
+ for (const cand of candidates) {
110
+ hit = latexWithin(dir, cand);
111
+ if (hit) { mime = LATEX_PREVIEW_IMAGE_TYPES[extOf(cand)]; break; }
112
+ }
113
+ if (!hit || !mime) { dropped.push(path.basename(src)); return whole; }
114
+ let size;
115
+ try { size = fs.statSync(hit).size; } catch { dropped.push(path.basename(src)); return whole; }
116
+ if (!Number.isFinite(size) || size > LATEX_PREVIEW_MAX_IMAGE || size > budget) { dropped.push(path.basename(src)); return whole; }
117
+ let data;
118
+ try { data = fs.readFileSync(hit); } catch { dropped.push(path.basename(src)); return whole; }
119
+ budget -= size;
120
+ inlined += 1;
121
+ return `src="data:${mime};base64,${data.toString("base64")}"`;
122
+ });
123
+ return { html: out, inlined, dropped: [...new Set(dropped)] };
124
+ }
125
+
126
+ /** Render one LaTeX source to HTML for the preview pane. */
127
+ export async function latexPreview(source, docPath, buffers, { pandoc, timeoutMs = LATEX_PREVIEW_TIMEOUT_MS } = {}) {
128
+ if (!scalar(source) || source.length === 0) return { ok: false, error: "No LaTeX source to preview." };
129
+ if (bytes(source) > LATEX_PREVIEW_MAX_SOURCE) return { ok: false, error: "This document is too large to preview while you type." };
130
+ const bin = pandoc;
131
+ if (!bin) return { ok: false, error: "pandoc is not installed, or CarmaR could not find it. Compile still works; only the live preview needs pandoc." };
132
+ let dir = "";
133
+ if (scalar(docPath) && docPath) { try { if (fs.existsSync(docPath)) dir = path.dirname(fs.realpathSync(docPath)); } catch { dir = ""; } }
134
+ const keep = {};
135
+ if (buffers && typeof buffers === "object" && !Array.isArray(buffers)) {
136
+ Object.keys(buffers).filter(Boolean).slice(0, LATEX_PREVIEW_MAX_FILES).forEach((k) => { if (typeof buffers[k] === "string") keep[k] = buffers[k]; });
137
+ }
138
+ const expanded = dir ? latexPreviewExpand(source, dir, keep) : source;
139
+ const started = process.hrtime.bigint();
140
+ const args = ["--from=latex", "--to=html5", "--mathml", "--wrap=none", "--standalone", "--sandbox"];
141
+ if (dir) latexPreviewBibliography(expanded, dir).forEach((bib) => args.push("--citeproc", `--bibliography=${bib}`));
142
+ // A 0600 temp file, as cite does; HOME and the data dir moved aside so a
143
+ // render never reads or writes this user's pandoc configuration.
144
+ const scratch = fs.mkdtempSync(path.join(os.tmpdir(), "carmar-preview-"));
145
+ const input = path.join(scratch, `carmar-preview-${crypto.randomBytes(4).toString("hex")}.tex`);
146
+ try {
147
+ fs.writeFileSync(input, expanded + "\n", { mode: 0o600 });
148
+ const ran = await run(bin, [...args, input], { timeoutMs, env: { ...process.env, PANDOC_DATA_DIR: scratch, HOME: scratch } });
149
+ if (ran.error) return { ok: false, error: /timeout/i.test(ran.error) ? "The preview took too long and was stopped." : `preview: ${ran.error}` };
150
+ if (ran.timeout) return { ok: false, error: "The preview took too long and was stopped." };
151
+ if (ran.status !== 0) {
152
+ let first = String(ran.stderr || "").split("\n")[0] || "";
153
+ const shown = scalar(docPath) && docPath ? path.basename(docPath) : "the document";
154
+ first = first.split(input).join(shown);
155
+ return { ok: false, partial: true, error: `pandoc could not read this document${first ? `: ${first}` : "."}` };
156
+ }
157
+ const html = ran.stdout || "";
158
+ if (bytes(html) > LATEX_PREVIEW_MAX_HTML) return { ok: false, error: "The rendered preview is too large to show." };
159
+ const figures = dir ? latexPreviewInline(html, dir) : { html, inlined: 0, dropped: [] };
160
+ return {
161
+ ok: true, html: figures.html, warnings: String(ran.stderr || "").trim(),
162
+ elapsed_ms: Number(process.hrtime.bigint() - started) / 1e6,
163
+ inlined: figures.inlined, dropped: figures.dropped,
164
+ };
165
+ } finally {
166
+ fs.rmSync(scratch, { recursive: true, force: true });
167
+ }
168
+ }
169
+
170
+ export function createPlane({ env, audit }) {
171
+ let pandocBin = null;
172
+ const pandoc = () => { if (pandocBin === null) pandocBin = findPandoc(env); return pandocBin; };
173
+ return {
174
+ name: "latex",
175
+ ops: OPS,
176
+ agentRefused: OPS,
177
+ agentReason: (op) => AGENT_WHY[op],
178
+ commands: OPS,
179
+ capabilities: ["latex-preview-v1"],
180
+ handle(cmd, rec, ctx) {
181
+ const { enc, scalarChr } = ctx;
182
+ if (rec.role !== "page" || !scalarChr(cmd.id)) return undefined;
183
+ const reply = (payload) => rec.ws.send(enc({ type: "latex_preview", id: cmd.id, ...payload }));
184
+ if (!PAGE_ONLY_CLASSES.includes(rec.class)) {
185
+ audit("latex-preview-refused", { reason: "class", class: rec.class || "unknown" });
186
+ reply({ error: "Only the local notebook page may render a preview." });
187
+ return undefined;
188
+ }
189
+ // `version` is the page's own token, echoed so it can drop a stale reply.
190
+ const version = cmd.version ?? null;
191
+ return latexPreview(cmd.source, cmd.path ?? "", cmd.buffers, { pandoc: pandoc() })
192
+ .catch((e) => ({ ok: false, error: e.message }))
193
+ .then((out) => {
194
+ if (out.ok !== true) reply({ error: out.error, partial: out.partial === true, version });
195
+ else reply({ ok: true, html: out.html, warnings: out.warnings, elapsed_ms: out.elapsed_ms, inlined: out.inlined, dropped: out.dropped, version });
196
+ });
197
+ },
198
+ };
199
+ }
200
+
201
+ export default { createPlane };