beatrina 0.8.6 → 0.9.43
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/NOTICES +1 -1
- package/README.md +6 -0
- package/beatrina_V0.9.43.html +1885 -0
- package/beatrina_V0.9.43.html.inputs.json +1 -0
- package/bin/beatrina.mjs +156 -26
- package/bin/browser.mjs +31 -0
- package/bin/cli.mjs +64 -5
- package/bin/doctor-rows.mjs +31 -0
- package/bin/failsafe.mjs +8 -3
- package/bin/finder.mjs +74 -0
- package/bin/lsquery.swift +55 -0
- package/bin/pages.mjs +81 -0
- package/bin/python-setup.mjs +207 -0
- package/bin/runtime-dirs.mjs +27 -0
- package/bin/sessions.mjs +30 -10
- package/bin/shortcut.mjs +271 -35
- package/bin/update-check.mjs +2 -2
- package/build-info.json +1 -1
- package/engines/js/worker.mjs +6 -3
- package/engines/python/adapter.py +14 -13
- package/engines/python/analyze.py +4 -2
- package/engines/python/bootstrap.py +32 -31
- package/engines/python/debugger.py +10 -6
- package/engines/python/engine.json +1 -1
- package/engines/python/handoff.py +4 -2
- package/engines/python/worker.py +39 -10
- package/engines/r/engine.json +1 -1
- package/engines/r/handoff.R +4 -3
- package/failsafe/ai-policy.R +16 -16
- package/failsafe/ai-store.R +6 -6
- package/failsafe/cite.R +11 -11
- package/failsafe/journal.R +11 -11
- package/failsafe/plugins.R +27 -27
- package/failsafe/serve.R +364 -239
- package/failsafe/session-documents.R +104 -0
- package/host/ai-authority.mjs +55 -0
- package/host/ai-policy.mjs +48 -26
- package/host/bundle.mjs +194 -0
- package/host/deployment.mjs +25 -18
- package/host/engine-js.mjs +16 -17
- package/host/engine-pool.mjs +56 -8
- package/host/engine-python.mjs +80 -31
- package/host/engine-r.mjs +38 -32
- package/host/engine-stdio.mjs +45 -12
- package/host/env-names.mjs +148 -0
- package/host/gateway-token.mjs +51 -0
- package/host/journal-store.mjs +10 -9
- package/host/main.mjs +216 -76
- package/host/payload.mjs +131 -0
- package/host/planes/README.md +1 -1
- package/host/planes/ai-store.mjs +6 -5
- package/host/planes/ai.mjs +49 -29
- package/host/planes/analyze.mjs +6 -5
- package/host/planes/bundle.mjs +344 -0
- package/host/planes/choose.mjs +404 -0
- package/host/planes/cite.mjs +18 -17
- package/host/planes/files.mjs +0 -0
- package/host/planes/jobs.mjs +43 -20
- package/host/planes/latex.mjs +41 -5
- package/host/planes/mcp.mjs +80 -91
- package/host/planes/pair.mjs +24 -24
- package/host/planes/pipe-term.mjs +2 -2
- package/host/planes/plugins.mjs +1 -1
- package/host/planes/recent-documents.mjs +170 -0
- package/host/planes/sessions.mjs +230 -82
- package/host/planes/settings.mjs +4 -4
- package/host/planes/terminal.mjs +13 -11
- package/host/planes/test-file.mjs +2 -2
- package/host/planes/update.mjs +6 -13
- package/host/plugin-store.mjs +35 -52
- package/host/recent-documents.mjs +124 -0
- package/host/runtime-dir.mjs +47 -0
- package/host/server.mjs +123 -21
- package/host/session-keep.mjs +70 -0
- package/host/settings.mjs +85 -37
- package/host/update-record.mjs +3 -2
- package/host/user-dirs.mjs +60 -18
- package/host/which.mjs +39 -0
- package/host/windows-runtime.mjs +6 -3
- package/host/worker-plane.mjs +80 -7
- package/host/ws.mjs +9 -2
- package/host/zip.mjs +237 -0
- package/kernel/analyze.R +1 -1
- package/{check → kernel/check}/acceptance.mjs +1 -1
- package/kernel/check/knit-file.mjs +19639 -0
- package/{check → kernel/check}/session.mjs +49 -8
- package/kernel/deployment.R +20 -20
- package/kernel/examples/NOTICE.md +1 -1
- package/kernel/fileio.R +6 -6
- package/kernel/index.html +2 -2
- package/kernel/job-run.R +83 -22
- package/kernel/jobs.R +28 -12
- package/kernel/kernel-version +1 -1
- package/kernel/kernel.R +24 -21
- package/kernel/knitr-run.R +50 -9
- package/kernel/latex.R +429 -30
- package/kernel/mcp/{carmar-mcp.mjs → beatrina-mcp.mjs} +249 -59
- package/kernel/notebook-page.R +7 -7
- package/kernel/project.R +10 -10
- package/kernel/settings.R +105 -56
- package/kernel/sniff.R +4 -4
- package/kernel/typst.R +121 -0
- package/kernel/worker.R +602 -123
- package/kernel/workspace-keep.R +188 -0
- package/lib/agent-authoring-contract.js +28 -19
- package/lib/cell-kinds.js +3 -3
- package/lib/engine-labels.js +4 -4
- package/menu/Beatrina Menu.app/Contents/Info.plist +14 -0
- package/menu/Beatrina Menu.app/Contents/MacOS/Beatrina Menu +0 -0
- package/menu/Beatrina Menu.app/Contents/_CodeSignature/CodeResources +115 -0
- package/package.json +4 -3
- package/carmar_V0.8.6.html +0 -1310
package/kernel/kernel.R
CHANGED
|
@@ -15,14 +15,14 @@ suppressPackageStartupMessages(library(jsonlite))
|
|
|
15
15
|
#' framework build RStudio uses, 1241 packages including tna and TraMineR) sat
|
|
16
16
|
#' unused. An IDE that cannot see your packages is not your IDE.
|
|
17
17
|
#'
|
|
18
|
-
#' Order: an explicit `
|
|
18
|
+
#' Order: an explicit `BEATRINA_RSCRIPT`, then the macOS framework R, then PATH.
|
|
19
19
|
#'
|
|
20
20
|
#' @return Path to an Rscript binary.
|
|
21
21
|
#' `explicit` is an argument rather than only an env read so serve.R can pass
|
|
22
22
|
#' the RESOLVED value (a user's rscript_path from settings, or the env var when
|
|
23
23
|
#' a deployment set one). Defaulting to the env var keeps every existing call
|
|
24
24
|
#' correct without changing it.
|
|
25
|
-
detect_rscript <- function(explicit = Sys.getenv("
|
|
25
|
+
detect_rscript <- function(explicit = Sys.getenv("BEATRINA_RSCRIPT", "")) {
|
|
26
26
|
if (nzchar(explicit) && file.exists(explicit)) return(explicit)
|
|
27
27
|
# The same ladder tools/app/launch.sh walks, and for the same reason: this
|
|
28
28
|
# process may itself have been started by a Finder launch, whose PATH is
|
|
@@ -66,7 +66,7 @@ detect_r_binary <- function(rscript) {
|
|
|
66
66
|
#'
|
|
67
67
|
#' Command-line R promotes itself to a foreground LaunchServices application
|
|
68
68
|
#' when its Aqua/AppKit support initializes. On macOS 26 that gives every
|
|
69
|
-
#' document worker a generic `exec` Dock tile. A packaged
|
|
69
|
+
#' document worker a generic `exec` Dock tile. A packaged Beatrina kernel carries
|
|
70
70
|
#' a tiny native library beside kernel.R; calling it before worker.R is
|
|
71
71
|
#' sourced registers this process as BackgroundOnly first. Distributions may
|
|
72
72
|
#' carry either an architecture-specific marker (R packages) or one universal
|
|
@@ -92,10 +92,10 @@ macos_background_boot <- function(worker_path,
|
|
|
92
92
|
# R_checkActivityEx, wedging the whole session at 0% CPU with Stop unable to
|
|
93
93
|
# reach it. Foreground R, which is what RStudio runs, never does this. So a
|
|
94
94
|
# visible Dock icon is the price of a kernel that answers; set
|
|
95
|
-
#
|
|
95
|
+
# BEATRINA_MARK_BACKGROUND=1 to restore Dock hiding once a transform that does
|
|
96
96
|
# not wedge the console read is found.
|
|
97
|
-
if (!identical(Sys.getenv("
|
|
98
|
-
marker <- Sys.getenv("
|
|
97
|
+
if (!identical(Sys.getenv("BEATRINA_MARK_BACKGROUND", "0"), "1")) return("")
|
|
98
|
+
marker <- Sys.getenv("BEATRINA_BACKGROUND_LIBRARY", "")
|
|
99
99
|
if (!nzchar(marker)) {
|
|
100
100
|
suffix <- if (grepl("^(aarch64|arm64)", arch, ignore.case = TRUE)) {
|
|
101
101
|
"arm64"
|
|
@@ -105,8 +105,8 @@ macos_background_boot <- function(worker_path,
|
|
|
105
105
|
beside <- dirname(worker_path)
|
|
106
106
|
candidates <- c(
|
|
107
107
|
if (nzchar(suffix)) file.path(
|
|
108
|
-
beside, paste0("
|
|
109
|
-
file.path(beside, "
|
|
108
|
+
beside, paste0("beatrina-background-", suffix, ".dylib")),
|
|
109
|
+
file.path(beside, "beatrina-background.dylib"))
|
|
110
110
|
found <- candidates[file.exists(candidates)]
|
|
111
111
|
marker <- if (length(found)) found[[1L]] else candidates[[1L]]
|
|
112
112
|
}
|
|
@@ -116,7 +116,7 @@ macos_background_boot <- function(worker_path,
|
|
|
116
116
|
# already background-only; the call still causes the early LaunchServices
|
|
117
117
|
# registration we need. Classification, not that legacy status code, is the
|
|
118
118
|
# contract, so the result is intentionally ignored.
|
|
119
|
-
sprintf('local({dyn.load(%s);.C("
|
|
119
|
+
sprintf('local({dyn.load(%s);.C("beatrina_mark_background",result=integer(1));invisible(NULL)})',
|
|
120
120
|
marker)
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -127,7 +127,7 @@ macos_background_boot <- function(worker_path,
|
|
|
127
127
|
#' if absent; user code cannot guess it, so it cannot forge a frame.
|
|
128
128
|
#' @return A kernel handle: list(proc, sentinel, buffer).
|
|
129
129
|
#' @param env_extra Named character vector of environment variables to add for
|
|
130
|
-
#' the worker — e.g. `c(
|
|
130
|
+
#' the worker — e.g. `c(BEATRINA_ROOT = "/srv/project")`, which confines every
|
|
131
131
|
#' file command to that subtree. Managed deployments set it; tests set it to
|
|
132
132
|
#' prove the confinement holds.
|
|
133
133
|
#' A UTF-8 character locale, whatever the launcher handed us.
|
|
@@ -243,19 +243,21 @@ kernel_start <- function(worker_path, sentinel = NULL, rscript = detect_rscript(
|
|
|
243
243
|
# The worker cannot read argv for these — `R --interactive` takes no
|
|
244
244
|
# script argument, the worker is booted by a sys.source line fed through
|
|
245
245
|
# stdin below — so they travel in the environment instead.
|
|
246
|
-
clean_env[["
|
|
247
|
-
clean_env[["
|
|
248
|
-
clean_env[["
|
|
249
|
-
|
|
246
|
+
clean_env[["BEATRINA_SENTINEL"]] <- sentinel
|
|
247
|
+
clean_env[["BEATRINA_CMD_TAG"]] <- cmdtag
|
|
248
|
+
clean_env[["BEATRINA_WORKER_MODE"]] <- "interactive"
|
|
249
|
+
# readline's screen width: at 80 a long command line echoes as "<" + tail with
|
|
250
|
+
# no cmdtag (Linux; host/engine-r.mjs has the measurement). The boot line gives
|
|
251
|
+
# user code back its own COLUMNS.
|
|
252
|
+
clean_env[["BEATRINA_USER_COLUMNS"]] <- Sys.getenv("COLUMNS", "")
|
|
253
|
+
clean_env[["COLUMNS"]] <- "100000"
|
|
254
|
+
clean_env[["BEATRINA_WORKER_DIR"]] <- dirname(normalizePath(worker_path, mustWork = FALSE))
|
|
250
255
|
}
|
|
251
256
|
spawn_bin <- if (identical(mode, "interactive")) r_binary else rscript
|
|
252
257
|
spawn_args <- if (identical(mode, "interactive")) {
|
|
253
258
|
# --no-echo suppresses the "> " prompt; the input ECHO it does not suppress
|
|
254
259
|
# is scrubbed in kernel_poll by the cmdtag / pending-echo machinery.
|
|
255
|
-
|
|
256
|
-
# R echoes each line read — on Linux as a scrolled "<" + tail that carries no
|
|
257
|
-
# cmdtag (host/engine-r.mjs has the measurement).
|
|
258
|
-
c("--no-readline", "--interactive", "--no-echo", "--no-save", "--no-restore", "--no-site-file")
|
|
260
|
+
c("--interactive", "--no-echo", "--no-save", "--no-restore", "--no-site-file")
|
|
259
261
|
} else {
|
|
260
262
|
# --vanilla, NOT --no-init-file: the user's .Rprofile/.Renviron are part of
|
|
261
263
|
# their R (library paths, repos, options). Only history and saved workspaces
|
|
@@ -287,12 +289,13 @@ kernel_start <- function(worker_path, sentinel = NULL, rscript = detect_rscript(
|
|
|
287
289
|
# rationale (parse the ~2,000-line file in one pass instead of feeding it
|
|
288
290
|
# through the REPL reader). worker.R itself is sourced, not the shim —
|
|
289
291
|
# the shim's only job was resolving its own directory from --file=,
|
|
290
|
-
# which
|
|
292
|
+
# which BEATRINA_WORKER_DIR now carries.
|
|
291
293
|
real_worker <- file.path(dirname(worker_path), "worker.R")
|
|
292
294
|
if (!file.exists(real_worker)) real_worker <- worker_path
|
|
293
295
|
worker_boot <- sprintf('sys.source("%s", envir = globalenv(), keep.source = FALSE)',
|
|
294
296
|
encodeString(normalizePath(real_worker)))
|
|
295
|
-
|
|
297
|
+
width_boot <- 'local({ cols <- Sys.getenv("BEATRINA_USER_COLUMNS"); Sys.unsetenv("BEATRINA_USER_COLUMNS"); if (nzchar(cols)) Sys.setenv(COLUMNS = cols) else Sys.unsetenv("COLUMNS") })'
|
|
298
|
+
boot <- paste(Filter(nzchar, c(width_boot, macos_background_boot(worker_path), worker_boot)),
|
|
296
299
|
collapse = ";")
|
|
297
300
|
kernel_console(k, boot)
|
|
298
301
|
}
|
|
@@ -359,7 +362,7 @@ kernel_command <- function(k, json) {
|
|
|
359
362
|
return(kernel_write(k, paste0(json, "\n")))
|
|
360
363
|
}
|
|
361
364
|
if (nchar(json, type = "bytes") > MAX_CONSOLE_LINE) {
|
|
362
|
-
spill <- tempfile("
|
|
365
|
+
spill <- tempfile("beatrina-cmd-", fileext = ".json")
|
|
363
366
|
writeLines(json, spill, useBytes = TRUE)
|
|
364
367
|
Sys.chmod(spill, mode = "0600")
|
|
365
368
|
json <- toJSON(list(type = "cmdfile", path = spill), auto_unbox = TRUE)
|
package/kernel/knitr-run.R
CHANGED
|
@@ -6,7 +6,7 @@ KNIT_DOCS <- new.env(parent = emptyenv())
|
|
|
6
6
|
KNIT_BASE <- NULL
|
|
7
7
|
KNIT_CAPTURE <- NULL
|
|
8
8
|
KNIT_PROTOCOL <- FALSE
|
|
9
|
-
KNIT_MARKER <- "
|
|
9
|
+
KNIT_MARKER <- "BEATRINA_KNIT_OUTPUT_V1:"
|
|
10
10
|
|
|
11
11
|
run_document_cell <- function(id, source, document, dims = NULL, srcname = NULL) {
|
|
12
12
|
KNIT_PROTOCOL <<- TRUE
|
|
@@ -161,17 +161,38 @@ run_document_cell <- function(id, source, document, dims = NULL, srcname = NULL)
|
|
|
161
161
|
# Errors/interruption never become successful cached results.
|
|
162
162
|
chunk_options$error <- FALSE
|
|
163
163
|
chunk_options$engine <- "R"
|
|
164
|
-
chunk_options$
|
|
165
|
-
chunk_options$
|
|
166
|
-
chunk_options$
|
|
167
|
-
|
|
164
|
+
chunk_options$beatrina.adapter <- 1L
|
|
165
|
+
chunk_options$beatrina.document <- if (is.character(document$cacheId) && length(document$cacheId) == 1L && nzchar(document$cacheId)) document$cacheId else document$id
|
|
166
|
+
chunk_options$beatrina.setup <- setup_key
|
|
167
|
+
# A document saved on disk keeps its cache BESIDE it and keys it by its
|
|
168
|
+
# folder — where knitr and RStudio put `cache/` — not by wherever R's
|
|
169
|
+
# working directory happens to be. The window runs in the session's
|
|
170
|
+
# directory and `beatrina knit` in the document's, so keying by getwd()
|
|
171
|
+
# meant the two never shared one cached chunk (2026-09-19). A document
|
|
172
|
+
# with no file (an Untitled draft, the page's own notebook) keeps getwd().
|
|
173
|
+
doc_dir <- if (is.character(document$cacheId) && length(document$cacheId) == 1L &&
|
|
174
|
+
nzchar(document$cacheId) && file.exists(document$cacheId) && !dir.exists(document$cacheId))
|
|
175
|
+
dirname(normalizePath(document$cacheId)) else ""
|
|
176
|
+
chunk_options$beatrina.cwd <- if (nzchar(doc_dir)) doc_dir else getwd()
|
|
177
|
+
if (nzchar(doc_dir)) {
|
|
178
|
+
cache_prefix <- chunk_options$cache.path %||% opts$cache.path %||% "cache/"
|
|
179
|
+
if (is.character(cache_prefix) && length(cache_prefix) == 1L && nzchar(cache_prefix) &&
|
|
180
|
+
!grepl("^(/|~|[A-Za-z]:[/\\\\])", cache_prefix))
|
|
181
|
+
chunk_options$cache.path <- file.path(doc_dir, cache_prefix)
|
|
182
|
+
}
|
|
168
183
|
# Selection runs are native, uncached, but still inherit setup defaults.
|
|
169
184
|
if (isTRUE(document$selection)) chunk_options$cache <- FALSE
|
|
185
|
+
# One run that evaluates even though the cache holds this chunk, and
|
|
186
|
+
# refreshes the cache with what it produces: knitr's own cache.rebuild,
|
|
187
|
+
# for this run only (the page sets `rebuild` per submission, never in
|
|
188
|
+
# the header). A "cached" outcome means the code did not execute, so
|
|
189
|
+
# nothing it does to the session happened; this is the way to make it.
|
|
190
|
+
if (isTRUE(document$rebuild)) chunk_options$cache.rebuild <- TRUE
|
|
170
191
|
if (!is.null(dims$format) && identical(dims$format, "svg")) chunk_options$dev <- "svg"
|
|
171
192
|
# Local options are a public knitr template, not opts_chunk globals.
|
|
172
193
|
# Code reading opts_chunk sees document defaults; opts_current sees its
|
|
173
194
|
# effective local options. A local override never leaks to the next cell.
|
|
174
|
-
knitr::opts_template$set(.
|
|
195
|
+
knitr::opts_template$set(.beatrina_interactive = chunk_options)
|
|
175
196
|
merged <- modifyList(opts, chunk_options, keep.null = TRUE)
|
|
176
197
|
if (!is.language(merged$eval) && (!is.logical(merged$eval) || length(merged$eval) != 1L || is.na(merged$eval)))
|
|
177
198
|
stop("Interactive eval must be TRUE or FALSE. Use a full knitr render for expression-index selections.")
|
|
@@ -195,9 +216,9 @@ run_document_cell <- function(id, source, document, dims = NULL, srcname = NULL)
|
|
|
195
216
|
auto_unbox = TRUE, null = "null", na = "null", digits = NA))))
|
|
196
217
|
})
|
|
197
218
|
# Source stays the REAL chunk source so knitr discovers/cache-restores
|
|
198
|
-
# the correct R objects. The evaluator hook retains
|
|
219
|
+
# the correct R objects. The evaluator hook retains Beatrina's native path.
|
|
199
220
|
fence <- paste(rep("`", max(3L, max(nchar(regmatches(source, gregexpr("`+", source))[[1L]]), 0L) + 1L)), collapse = "")
|
|
200
|
-
input <- paste0(fence, "{r ", label, ", opts.label='.
|
|
221
|
+
input <- paste0(fence, "{r ", label, ", opts.label='.beatrina_interactive'}\n", source, "\n", fence, "\n")
|
|
201
222
|
output <- withCallingHandlers(knitr::knit(text = input, envir = globalenv(), quiet = TRUE),
|
|
202
223
|
message = function(m) {
|
|
203
224
|
# The native traceback already names the real chunk/source. knitr's
|
|
@@ -206,6 +227,20 @@ run_document_cell <- function(id, source, document, dims = NULL, srcname = NULL)
|
|
|
206
227
|
if (grepl("^\\s*Quitting from", conditionMessage(m))) invokeRestart("muffleMessage")
|
|
207
228
|
})
|
|
208
229
|
if (!evaluated && identical(output, input)) stop("knitr did not recognize the generated R chunk fence.")
|
|
230
|
+
# A rebuild rewrites the chunk's lazy-load database IN PLACE (same
|
|
231
|
+
# label, same hash, same file name). R keeps the bytes of every .rdb it
|
|
232
|
+
# has fetched from in a process-wide cache keyed by file name, so the
|
|
233
|
+
# next cache hit would index the fresh .rdx into the STALE bytes and
|
|
234
|
+
# every restored object would fail with "lazy-load database … is
|
|
235
|
+
# corrupt" (reproduced with plain knitr, 2026-09-17). Flushing that
|
|
236
|
+
# cache for the rewritten files is the only remedy R offers; base does
|
|
237
|
+
# not export it, tools:::makeLazyLoadDB calls the same internal.
|
|
238
|
+
if (evaluated && isTRUE(document$rebuild)) {
|
|
239
|
+
stem <- paste0(merged$cache.path %||% "cache/", label, "_")
|
|
240
|
+
rdb <- list.files(dirname(stem), full.names = TRUE,
|
|
241
|
+
pattern = paste0("^", gsub("([][{}()+*^$|\\\\?.])", "\\\\\\1", basename(stem)), "[0-9a-f]+\\.rdb$"))
|
|
242
|
+
invisible(lapply(rdb, \(f) .Internal(lazyLoadDBflush(normalizePath(f)))))
|
|
243
|
+
}
|
|
209
244
|
cache_hit <- !evaluated && !identical(merged$eval, FALSE)
|
|
210
245
|
if (!evaluated && identical(merged$eval, FALSE)) {
|
|
211
246
|
terminal$status <- "skipped"
|
|
@@ -213,7 +248,13 @@ run_document_cell <- function(id, source, document, dims = NULL, srcname = NULL)
|
|
|
213
248
|
terminal$reason <- terminal$message
|
|
214
249
|
}
|
|
215
250
|
if (cache_hit && startsWith(trimws(output), KNIT_MARKER)) {
|
|
216
|
-
|
|
251
|
+
# `last` is NOT redundant: substring()'s default last is 1000000L, so the
|
|
252
|
+
# two-argument form cut every payload over 1 MB mid-quantum and the
|
|
253
|
+
# replay died with "Error in base64 decode". One 11x10in figure is
|
|
254
|
+
# enough (PNG -> base64 -> JSON -> base64 is ~1.85x). Same trap as the
|
|
255
|
+
# payload slice in kernel.R.
|
|
256
|
+
stored <- trimws(output)
|
|
257
|
+
payload <- substring(stored, nchar(KNIT_MARKER) + 1L, nchar(stored))
|
|
217
258
|
restored <- jsonlite::fromJSON(rawToChar(jsonlite::base64_dec(payload)), simplifyVector = FALSE)
|
|
218
259
|
for (frame in restored) { frame$id <- id; emit(frame) }
|
|
219
260
|
}
|