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.
Files changed (112) hide show
  1. package/NOTICES +1 -1
  2. package/README.md +6 -0
  3. package/beatrina_V0.9.43.html +1885 -0
  4. package/beatrina_V0.9.43.html.inputs.json +1 -0
  5. package/bin/beatrina.mjs +156 -26
  6. package/bin/browser.mjs +31 -0
  7. package/bin/cli.mjs +64 -5
  8. package/bin/doctor-rows.mjs +31 -0
  9. package/bin/failsafe.mjs +8 -3
  10. package/bin/finder.mjs +74 -0
  11. package/bin/lsquery.swift +55 -0
  12. package/bin/pages.mjs +81 -0
  13. package/bin/python-setup.mjs +207 -0
  14. package/bin/runtime-dirs.mjs +27 -0
  15. package/bin/sessions.mjs +30 -10
  16. package/bin/shortcut.mjs +271 -35
  17. package/bin/update-check.mjs +2 -2
  18. package/build-info.json +1 -1
  19. package/engines/js/worker.mjs +6 -3
  20. package/engines/python/adapter.py +14 -13
  21. package/engines/python/analyze.py +4 -2
  22. package/engines/python/bootstrap.py +32 -31
  23. package/engines/python/debugger.py +10 -6
  24. package/engines/python/engine.json +1 -1
  25. package/engines/python/handoff.py +4 -2
  26. package/engines/python/worker.py +39 -10
  27. package/engines/r/engine.json +1 -1
  28. package/engines/r/handoff.R +4 -3
  29. package/failsafe/ai-policy.R +16 -16
  30. package/failsafe/ai-store.R +6 -6
  31. package/failsafe/cite.R +11 -11
  32. package/failsafe/journal.R +11 -11
  33. package/failsafe/plugins.R +27 -27
  34. package/failsafe/serve.R +364 -239
  35. package/failsafe/session-documents.R +104 -0
  36. package/host/ai-authority.mjs +55 -0
  37. package/host/ai-policy.mjs +48 -26
  38. package/host/bundle.mjs +194 -0
  39. package/host/deployment.mjs +25 -18
  40. package/host/engine-js.mjs +16 -17
  41. package/host/engine-pool.mjs +56 -8
  42. package/host/engine-python.mjs +80 -31
  43. package/host/engine-r.mjs +38 -32
  44. package/host/engine-stdio.mjs +45 -12
  45. package/host/env-names.mjs +148 -0
  46. package/host/gateway-token.mjs +51 -0
  47. package/host/journal-store.mjs +10 -9
  48. package/host/main.mjs +216 -76
  49. package/host/payload.mjs +131 -0
  50. package/host/planes/README.md +1 -1
  51. package/host/planes/ai-store.mjs +6 -5
  52. package/host/planes/ai.mjs +49 -29
  53. package/host/planes/analyze.mjs +6 -5
  54. package/host/planes/bundle.mjs +344 -0
  55. package/host/planes/choose.mjs +404 -0
  56. package/host/planes/cite.mjs +18 -17
  57. package/host/planes/files.mjs +0 -0
  58. package/host/planes/jobs.mjs +43 -20
  59. package/host/planes/latex.mjs +41 -5
  60. package/host/planes/mcp.mjs +80 -91
  61. package/host/planes/pair.mjs +24 -24
  62. package/host/planes/pipe-term.mjs +2 -2
  63. package/host/planes/plugins.mjs +1 -1
  64. package/host/planes/recent-documents.mjs +170 -0
  65. package/host/planes/sessions.mjs +230 -82
  66. package/host/planes/settings.mjs +4 -4
  67. package/host/planes/terminal.mjs +13 -11
  68. package/host/planes/test-file.mjs +2 -2
  69. package/host/planes/update.mjs +6 -13
  70. package/host/plugin-store.mjs +35 -52
  71. package/host/recent-documents.mjs +124 -0
  72. package/host/runtime-dir.mjs +47 -0
  73. package/host/server.mjs +123 -21
  74. package/host/session-keep.mjs +70 -0
  75. package/host/settings.mjs +85 -37
  76. package/host/update-record.mjs +3 -2
  77. package/host/user-dirs.mjs +60 -18
  78. package/host/which.mjs +39 -0
  79. package/host/windows-runtime.mjs +6 -3
  80. package/host/worker-plane.mjs +80 -7
  81. package/host/ws.mjs +9 -2
  82. package/host/zip.mjs +237 -0
  83. package/kernel/analyze.R +1 -1
  84. package/{check → kernel/check}/acceptance.mjs +1 -1
  85. package/kernel/check/knit-file.mjs +19639 -0
  86. package/{check → kernel/check}/session.mjs +49 -8
  87. package/kernel/deployment.R +20 -20
  88. package/kernel/examples/NOTICE.md +1 -1
  89. package/kernel/fileio.R +6 -6
  90. package/kernel/index.html +2 -2
  91. package/kernel/job-run.R +83 -22
  92. package/kernel/jobs.R +28 -12
  93. package/kernel/kernel-version +1 -1
  94. package/kernel/kernel.R +24 -21
  95. package/kernel/knitr-run.R +50 -9
  96. package/kernel/latex.R +429 -30
  97. package/kernel/mcp/{carmar-mcp.mjs → beatrina-mcp.mjs} +249 -59
  98. package/kernel/notebook-page.R +7 -7
  99. package/kernel/project.R +10 -10
  100. package/kernel/settings.R +105 -56
  101. package/kernel/sniff.R +4 -4
  102. package/kernel/typst.R +121 -0
  103. package/kernel/worker.R +602 -123
  104. package/kernel/workspace-keep.R +188 -0
  105. package/lib/agent-authoring-contract.js +28 -19
  106. package/lib/cell-kinds.js +3 -3
  107. package/lib/engine-labels.js +4 -4
  108. package/menu/Beatrina Menu.app/Contents/Info.plist +14 -0
  109. package/menu/Beatrina Menu.app/Contents/MacOS/Beatrina Menu +0 -0
  110. package/menu/Beatrina Menu.app/Contents/_CodeSignature/CodeResources +115 -0
  111. package/package.json +4 -3
  112. 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 `CARMAR_RSCRIPT`, then the macOS framework R, then PATH.
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("CARMAR_RSCRIPT", "")) {
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 CarmaR kernel carries
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
- # CARMAR_MARK_BACKGROUND=1 to restore Dock hiding once a transform that does
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("CARMAR_MARK_BACKGROUND", "0"), "1")) return("")
98
- marker <- Sys.getenv("CARMAR_BACKGROUND_LIBRARY", "")
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("carmar-background-", suffix, ".dylib")),
109
- file.path(beside, "carmar-background.dylib"))
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("carmar_mark_background",result=integer(1));invisible(NULL)})',
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(CARMAR_ROOT = "/srv/project")`, which confines every
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[["CARMAR_SENTINEL"]] <- sentinel
247
- clean_env[["CARMAR_CMD_TAG"]] <- cmdtag
248
- clean_env[["CARMAR_WORKER_MODE"]] <- "interactive"
249
- clean_env[["CARMAR_WORKER_DIR"]] <- dirname(normalizePath(worker_path, mustWork = FALSE))
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
- # --no-readline FIRST: R ignores it after --interactive, and with readline
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 CARMAR_WORKER_DIR now carries.
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
- boot <- paste(Filter(nzchar, c(macos_background_boot(worker_path), worker_boot)),
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("carmar-cmd-", fileext = ".json")
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)
@@ -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 <- "CARMAR_KNIT_OUTPUT_V1:"
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$carmar.adapter <- 1L
165
- chunk_options$carmar.document <- if (is.character(document$cacheId) && length(document$cacheId) == 1L && nzchar(document$cacheId)) document$cacheId else document$id
166
- chunk_options$carmar.setup <- setup_key
167
- chunk_options$carmar.cwd <- getwd()
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(.carmar_interactive = chunk_options)
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 CarmaR's native path.
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='.carmar_interactive'}\n", source, "\n", fence, "\n")
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
- payload <- substring(trimws(output), nchar(KNIT_MARKER) + 1L)
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
  }