beatrina 0.8.7 → 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 (111) 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 +152 -24
  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 +250 -42
  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 +241 -241
  35. package/host/ai-authority.mjs +55 -0
  36. package/host/ai-policy.mjs +48 -26
  37. package/host/bundle.mjs +194 -0
  38. package/host/deployment.mjs +25 -18
  39. package/host/engine-js.mjs +12 -17
  40. package/host/engine-pool.mjs +48 -6
  41. package/host/engine-python.mjs +80 -31
  42. package/host/engine-r.mjs +20 -21
  43. package/host/engine-stdio.mjs +35 -10
  44. package/host/env-names.mjs +148 -0
  45. package/host/gateway-token.mjs +51 -0
  46. package/host/journal-store.mjs +10 -9
  47. package/host/main.mjs +193 -77
  48. package/host/payload.mjs +131 -0
  49. package/host/planes/README.md +1 -1
  50. package/host/planes/ai-store.mjs +6 -5
  51. package/host/planes/ai.mjs +49 -29
  52. package/host/planes/analyze.mjs +6 -5
  53. package/host/planes/bundle.mjs +344 -0
  54. package/host/planes/choose.mjs +404 -0
  55. package/host/planes/cite.mjs +18 -17
  56. package/host/planes/files.mjs +0 -0
  57. package/host/planes/jobs.mjs +43 -20
  58. package/host/planes/latex.mjs +15 -5
  59. package/host/planes/mcp.mjs +80 -91
  60. package/host/planes/pair.mjs +24 -24
  61. package/host/planes/pipe-term.mjs +2 -2
  62. package/host/planes/plugins.mjs +1 -1
  63. package/host/planes/recent-documents.mjs +170 -0
  64. package/host/planes/sessions.mjs +226 -82
  65. package/host/planes/settings.mjs +4 -4
  66. package/host/planes/terminal.mjs +13 -11
  67. package/host/planes/test-file.mjs +2 -2
  68. package/host/planes/update.mjs +6 -13
  69. package/host/plugin-store.mjs +35 -52
  70. package/host/recent-documents.mjs +124 -0
  71. package/host/runtime-dir.mjs +47 -0
  72. package/host/server.mjs +75 -18
  73. package/host/session-keep.mjs +1 -1
  74. package/host/settings.mjs +85 -37
  75. package/host/update-record.mjs +3 -2
  76. package/host/user-dirs.mjs +60 -18
  77. package/host/which.mjs +39 -0
  78. package/host/windows-runtime.mjs +6 -3
  79. package/host/worker-plane.mjs +34 -6
  80. package/host/ws.mjs +9 -2
  81. package/host/zip.mjs +237 -0
  82. package/kernel/analyze.R +1 -1
  83. package/{check → kernel/check}/acceptance.mjs +1 -1
  84. package/kernel/check/knit-file.mjs +19639 -0
  85. package/{check → kernel/check}/session.mjs +49 -8
  86. package/kernel/deployment.R +20 -20
  87. package/kernel/examples/NOTICE.md +1 -1
  88. package/kernel/fileio.R +6 -6
  89. package/kernel/index.html +2 -2
  90. package/kernel/job-run.R +83 -22
  91. package/kernel/jobs.R +28 -12
  92. package/kernel/kernel-version +1 -1
  93. package/kernel/kernel.R +18 -18
  94. package/kernel/knitr-run.R +50 -9
  95. package/kernel/latex.R +128 -30
  96. package/kernel/mcp/{carmar-mcp.mjs → beatrina-mcp.mjs} +249 -59
  97. package/kernel/notebook-page.R +7 -7
  98. package/kernel/project.R +10 -10
  99. package/kernel/settings.R +105 -56
  100. package/kernel/sniff.R +4 -4
  101. package/kernel/typst.R +121 -0
  102. package/kernel/worker.R +522 -119
  103. package/kernel/workspace-keep.R +3 -3
  104. package/lib/agent-authoring-contract.js +28 -19
  105. package/lib/cell-kinds.js +3 -3
  106. package/lib/engine-labels.js +4 -4
  107. package/menu/Beatrina Menu.app/Contents/Info.plist +14 -0
  108. package/menu/Beatrina Menu.app/Contents/MacOS/Beatrina Menu +0 -0
  109. package/menu/Beatrina Menu.app/Contents/_CodeSignature/CodeResources +115 -0
  110. package/package.json +4 -3
  111. package/carmar_V0.8.7.html +0 -1522
package/failsafe/serve.R CHANGED
@@ -19,17 +19,17 @@
19
19
  # before its event loop starts. The option guards this process's one re-entry.
20
20
  # Rscript writes a space in the script path as `~+~` when it builds R's
21
21
  # command line, and R decodes it only to open the file: commandArgs(FALSE)
22
- # still carries the encoded form. "CarmaR Helper.app" has a space, so its
22
+ # still carries the encoded form. "Beatrina Helper.app" has a space, so its
23
23
  # kernel resolved `here` to a path that does not exist and died at source().
24
24
  # Decoded once, here, for every reader of --file= in this file.
25
- carmar_file_arg <- function() {
25
+ beatrina_file_arg <- function() {
26
26
  gsub("~+~", " ", sub("^--file=", "",
27
27
  grep("^--file=", commandArgs(FALSE), value = TRUE)[1L]), fixed = TRUE)
28
28
  }
29
- if (!isTRUE(getOption("carmar.supervisor.parsed"))) {
30
- supervisor_entry <- carmar_file_arg()
29
+ if (!isTRUE(getOption("beatrina.supervisor.parsed"))) {
30
+ supervisor_entry <- beatrina_file_arg()
31
31
  supervisor_program <- parse(file = supervisor_entry, keep.source = FALSE)
32
- options(carmar.supervisor.parsed = TRUE)
32
+ options(beatrina.supervisor.parsed = TRUE)
33
33
  eval(supervisor_program, envir = globalenv())
34
34
  quit(save = "no", status = 0L, runLast = FALSE)
35
35
  }
@@ -39,7 +39,7 @@ suppressPackageStartupMessages({
39
39
  library(jsonlite)
40
40
  })
41
41
 
42
- here <- dirname(normalizePath(carmar_file_arg()))
42
+ here <- dirname(normalizePath(beatrina_file_arg()))
43
43
  source(file.path(here, "kernel.R"))
44
44
  source(file.path(here, "notebook-page.R"))
45
45
  source(file.path(here, "deployment.R"))
@@ -52,7 +52,7 @@ source(file.path(here, "settings.R"))
52
52
  # The file system: one body shared with the worker (docs/document-model-plan.md
53
53
  # D3). The supervisor answers the pure file ops itself so a Save never waits
54
54
  # behind a running cell; the worker keeps the same functions for the
55
- # transition flag CARMAR_FILE_OPS=worker.
55
+ # transition flag BEATRINA_FILE_OPS=worker.
56
56
  source(file.path(here, "fileio.R"))
57
57
  source(file.path(here, "journal.R"))
58
58
  source(file.path(here, "ai-store.R"))
@@ -65,7 +65,7 @@ source(file.path(here, "session-documents.R"))
65
65
  # Apply an administrator-owned desktop policy before any subsystem reads its
66
66
  # environment. Invalid or user-writable policy fails closed at the shared
67
67
  # startup gate below; no partial values are applied.
68
- managed_config <- carmar_managed_environment()
68
+ managed_config <- beatrina_managed_environment()
69
69
 
70
70
  # Before anything reads or writes a frame. The supervisor parses every browser
71
71
  # message and re-encodes every worker reply, so its own character locale
@@ -85,16 +85,16 @@ read_kernel_protocol <- function() {
85
85
  candidates <- c(file.path(here, "kernel-protocol"),
86
86
  file.path(here, "..", "kernel.protocol"))
87
87
  hit <- candidates[file.exists(candidates)][1L]
88
- if (!length(hit) || is.na(hit)) stop("CarmaR kernel protocol stamp is missing.")
88
+ if (!length(hit) || is.na(hit)) stop("Beatrina kernel protocol stamp is missing.")
89
89
  value <- suppressWarnings(as.integer(trimws(readLines(hit, warn = FALSE, n = 1L))))
90
90
  if (length(value) != 1L || is.na(value) || value < 1L) {
91
- stop("CarmaR kernel protocol stamp is invalid.")
91
+ stop("Beatrina kernel protocol stamp is invalid.")
92
92
  }
93
93
  value
94
94
  }
95
- CARMAR_PROTOCOL_VERSION <- read_kernel_protocol()
95
+ BEATRINA_PROTOCOL_VERSION <- read_kernel_protocol()
96
96
  read_kernel_build <- function() {
97
- from_env <- trimws(Sys.getenv("CARMAR_KERNEL_BUILD", ""))
97
+ from_env <- trimws(Sys.getenv("BEATRINA_KERNEL_BUILD", ""))
98
98
  if (nzchar(from_env)) return(from_env)
99
99
  stamp <- file.path(here, "kernel-version")
100
100
  if (file.exists(stamp)) {
@@ -109,25 +109,25 @@ read_kernel_build <- function() {
109
109
  }
110
110
  "unknown"
111
111
  }
112
- CARMAR_KERNEL_BUILD <- read_kernel_build()
112
+ BEATRINA_KERNEL_BUILD <- read_kernel_build()
113
113
 
114
- #' What CarmaR is INSTALLED as, right now — re-read on every call.
114
+ #' What Beatrina is INSTALLED as, right now — re-read on every call.
115
115
  #'
116
- #' `CARMAR_KERNEL_BUILD` is what this process was started as. The bundle it
116
+ #' `BEATRINA_KERNEL_BUILD` is what this process was started as. The bundle it
117
117
  #' lives in is swapped IN PLACE by an upgrade, so the stamp beside this file
118
118
  #' can name a newer (or, after a rollback, an older) build than the one
119
119
  #' running. Reading it fresh every time is what lets a session that predates
120
120
  #' an install say so — in /health, in the ready frame and in every heartbeat
121
121
  #' — instead of serving the old page in silence until someone quits it.
122
122
  #'
123
- #' When the build was dictated by CARMAR_KERNEL_BUILD (tests, the broker),
124
- #' the installed build is dictated the same way: CARMAR_INSTALLED_BUILD, or
123
+ #' When the build was dictated by BEATRINA_KERNEL_BUILD (tests, the broker),
124
+ #' the installed build is dictated the same way: BEATRINA_INSTALLED_BUILD, or
125
125
  #' the same value — a test kernel must never read "newer installed" off a
126
126
  #' source checkout's notebook.version.cjs.
127
127
  installed_build <- function() {
128
- dictated <- trimws(Sys.getenv("CARMAR_KERNEL_BUILD", ""))
128
+ dictated <- trimws(Sys.getenv("BEATRINA_KERNEL_BUILD", ""))
129
129
  if (nzchar(dictated)) {
130
- inst <- trimws(Sys.getenv("CARMAR_INSTALLED_BUILD", ""))
130
+ inst <- trimws(Sys.getenv("BEATRINA_INSTALLED_BUILD", ""))
131
131
  return(if (nzchar(inst)) inst else dictated)
132
132
  }
133
133
  stamp <- file.path(here, "kernel-version")
@@ -146,13 +146,13 @@ installed_build <- function() {
146
146
  # The build this kernel REPLACED, when it was started by a "Restart into"
147
147
  # handoff (session_handoff_spawn below). Told to /health for the first
148
148
  # minute and a half so the page that asked can recognise its successor.
149
- HANDOFF_FROM <- trimws(Sys.getenv("CARMAR_HANDOFF_FROM", ""))
149
+ HANDOFF_FROM <- trimws(Sys.getenv("BEATRINA_HANDOFF_FROM", ""))
150
150
 
151
151
  # Where to listen. 127.0.0.1 unless an operator says otherwise, and saying
152
152
  # otherwise inverts the trust model — see spike/deployment.R, which owns that
153
153
  # decision and refuses the unsafe spellings of it outright.
154
154
  host <- local({
155
- h <- trimws(Sys.getenv("CARMAR_BIND", "127.0.0.1"))
155
+ h <- trimws(Sys.getenv("BEATRINA_BIND", "127.0.0.1"))
156
156
  if (nzchar(h)) h else "127.0.0.1"
157
157
  })
158
158
 
@@ -162,7 +162,7 @@ host <- local({
162
162
  #' here. localStorage and sessionStorage are keyed by scheme://host:PORT, so a
163
163
  #' new port on every start is a new, empty storage on every start: the saved
164
164
  #' notebooks invisible, the chat history gone, the AI key asked for again. A
165
- #' user restarting the kernel three times has three separate CarmaRs and no way
165
+ #' user restarting the kernel three times has three separate Beatrinas and no way
166
166
  #' to know why the last one forgot everything.
167
167
  #'
168
168
  #' A fixed loopback port is not a weaker position than a random one — the
@@ -177,7 +177,7 @@ port_taken <- function(p) {
177
177
  error = function(e) NULL))
178
178
  if (is.null(con)) FALSE else { close(con); TRUE }
179
179
  }
180
- # CARMAR_PORT is a PREFERENCE by default and a PROMISE when asked.
180
+ # BEATRINA_PORT is a PREFERENCE by default and a PROMISE when asked.
181
181
  #
182
182
  # Moving to a random port when the wanted one is taken is right for a person
183
183
  # double-clicking the app: they get a session instead of an error, and the
@@ -186,23 +186,23 @@ port_taken <- function(p) {
186
186
  # is already written into a registry record and about to be handed to Caddy
187
187
  # as an upstream, so a kernel that quietly moves leaves BOTH pointing at
188
188
  # nothing and the reader gets a connection refused with no explanation
189
- # anywhere in the system. CARMAR_PORT_STRICT=1 turns the silent move into a
189
+ # anywhere in the system. BEATRINA_PORT_STRICT=1 turns the silent move into a
190
190
  # refusal, which is the only honest answer when the caller cannot be told.
191
191
  port <- local({
192
- wanted <- suppressWarnings(as.integer(Sys.getenv("CARMAR_PORT", "4747")))
192
+ wanted <- suppressWarnings(as.integer(Sys.getenv("BEATRINA_PORT", "4747")))
193
193
  if (is.na(wanted) || wanted < 1024L || wanted > 65535L) wanted <- 4747L
194
194
  if (!port_taken(wanted)) return(wanted)
195
195
  # A successor started by a handoff is spawned while its predecessor still
196
196
  # holds the port; it waits for the port to free rather than moving.
197
- wait_s <- suppressWarnings(as.numeric(Sys.getenv("CARMAR_WAIT_PORT", "0")))
197
+ wait_s <- suppressWarnings(as.numeric(Sys.getenv("BEATRINA_WAIT_PORT", "0")))
198
198
  if (is.finite(wait_s) && wait_s > 0) {
199
199
  deadline <- Sys.time() + wait_s
200
200
  while (port_taken(wanted) && Sys.time() < deadline) Sys.sleep(0.25) # a wait is a loop
201
201
  if (!port_taken(wanted)) return(wanted)
202
202
  }
203
- if (identical(Sys.getenv("CARMAR_PORT_STRICT", ""), "1")) {
204
- message("CarmaR refuses to start: CARMAR_PORT=", wanted, " is already in use ",
205
- "and CARMAR_PORT_STRICT=1 forbids moving to another port. Whoever ",
203
+ if (identical(Sys.getenv("BEATRINA_PORT_STRICT", ""), "1")) {
204
+ message("Beatrina refuses to start: BEATRINA_PORT=", wanted, " is already in use ",
205
+ "and BEATRINA_PORT_STRICT=1 forbids moving to another port. Whoever ",
206
206
  "chose this port is expecting to find the kernel on it.")
207
207
  quit(status = 2L, save = "no")
208
208
  }
@@ -212,27 +212,27 @@ port <- local({
212
212
  # The posture: bind, Origin allow-list, Host allow-list, and whether an
213
213
  # Origin-less client is a same-user native process or an unauthenticated
214
214
  # stranger. Computed once; every gate below reads a decided answer.
215
- deployment <- carmar_deployment(port)
215
+ deployment <- beatrina_deployment(port)
216
216
 
217
217
  # The administrator's AI policy, read once. Its errors join the deployment's
218
218
  # in the same startup gate and for the same reason: a governance control that
219
219
  # silently failed to apply is worse than one that refuses to start, because
220
220
  # nobody finds out until the data has already gone to the wrong vendor.
221
- ai_policy <- carmar_ai_policy()
221
+ ai_policy <- beatrina_ai_policy()
222
222
 
223
223
  # The plugin policy (spike/plugins.R): which kinds exist, whether the person
224
224
  # may install, which hosts a fetch may reach, where the administrator's set
225
225
  # is. Unlike the AI policy this one is enforcement — every fetch and every
226
226
  # write is the supervisor's — and it fails closed the same way: a policy
227
227
  # that names only junk refuses to start rather than permitting everything.
228
- plugin_policy <- carmar_plugin_policy()
228
+ plugin_policy <- beatrina_plugin_policy()
229
229
 
230
230
  startup_errors <- c(managed_config$errors, deployment$errors, ai_policy$errors, plugin_policy$errors)
231
231
  if (length(startup_errors)) {
232
232
  # Refusing to start is the correct outcome. A server that binds to the
233
- # network with the loopback trust model is not a degraded CarmaR, it is a
233
+ # network with the loopback trust model is not a degraded Beatrina, it is a
234
234
  # remote-execution service, and there is no safe way to serve it "for now".
235
- for (msg in startup_errors) message("CarmaR refuses to start: ", msg)
235
+ for (msg in startup_errors) message("Beatrina refuses to start: ", msg)
236
236
  quit(status = 2L, save = "no")
237
237
  }
238
238
 
@@ -251,10 +251,10 @@ origins_ok <- deployment$origins
251
251
  hosts_ok <- deployment$hosts
252
252
 
253
253
  # Optional audit log — one JSON object per line, for deployments that must be
254
- # able to answer "what did this session do?". Off unless CARMAR_LOG is set,
254
+ # able to answer "what did this session do?". Off unless BEATRINA_LOG is set,
255
255
  # because a notebook that silently records the user's code would be worse than
256
256
  # no logging at all.
257
- log_path <- Sys.getenv("CARMAR_LOG", "")
257
+ log_path <- Sys.getenv("BEATRINA_LOG", "")
258
258
  # pandoc, found once and kept. Absent stays absent for the session — a
259
259
  # pandoc installed mid-session shows up after a restart, which is the same
260
260
  # rule R itself applies to a package installed under a running session.
@@ -283,28 +283,28 @@ audit <- function(event, ...) {
283
283
  # successful settings_set so a later read reports the value that is actually
284
284
  # in force, and `linger_s` / `HISTORY_ENABLED` below are the two the running
285
285
  # kernel can honour immediately (see each one's `effect`).
286
- settings_state <- carmar_settings_resolve()
287
- settings_value <- function(key) carmar_settings_value(settings_state, key)
286
+ settings_state <- beatrina_settings_resolve()
287
+ settings_value <- function(key) beatrina_settings_value(settings_state, key)
288
288
  audit("settings", detail = "startup", status = settings_state$file$status,
289
289
  keys = length(settings_state$file$values))
290
290
 
291
291
  # What every child inherits from the user's settings, and which R to run.
292
292
  # Defined here so the three spawn sites cannot drift: a setting that reaches
293
293
  # the worker but not the restarted worker is the worst kind of half-applied.
294
- settings_child_env <- function() carmar_settings_child_env(settings_state)
294
+ settings_child_env <- function() beatrina_settings_child_env(settings_state)
295
295
  settings_rscript <- function() {
296
296
  chosen <- settings_value("rscript_path")
297
297
  detect_rscript(if (is.character(chosen) && nzchar(chosen)) chosen
298
- else Sys.getenv("CARMAR_RSCRIPT", ""))
298
+ else Sys.getenv("BEATRINA_RSCRIPT", ""))
299
299
  }
300
300
 
301
- # The launcher's state dir (CARMAR_STATE) or the package's: where this
301
+ # The launcher's state dir (BEATRINA_STATE) or the package's: where this
302
302
  # supervisor keeps the handoff log for a successor it spawns. Nothing of the
303
303
  # R session is ever written here — a worker starts fresh, always (see the
304
304
  # handoff section below).
305
305
  state_dir <- function() {
306
- state <- Sys.getenv("CARMAR_STATE", "")
307
- if (!nzchar(state)) state <- tools::R_user_dir("carmar", "data")
306
+ state <- Sys.getenv("BEATRINA_STATE", "")
307
+ if (!nzchar(state)) state <- tools::R_user_dir("beatrina", "data")
308
308
  state
309
309
  }
310
310
  start_execution_worker <- function() {
@@ -325,16 +325,16 @@ sockets$open <- list()
325
325
  # "connecting…" forever waiting for a frame that was broadcast to nobody.
326
326
  sockets$hello <- NULL
327
327
  # A kept workspace for the NEXT ready worker: set by `restart {resume}` or, in a
328
- # handoff successor, by CARMAR_RESUME_TOKEN. Consumed once, on `ready`.
329
- sockets$resume_pending <- if (keep_token_ok(Sys.getenv("CARMAR_RESUME_TOKEN")))
330
- Sys.getenv("CARMAR_RESUME_TOKEN") else NULL
328
+ # handoff successor, by BEATRINA_RESUME_TOKEN. Consumed once, on `ready`.
329
+ sockets$resume_pending <- if (keep_token_ok(Sys.getenv("BEATRINA_RESUME_TOKEN")))
330
+ Sys.getenv("BEATRINA_RESUME_TOKEN") else NULL
331
331
  # The restore's report, kept for 120 s so a page that follows a handoff — and
332
332
  # connects after the restore finished — still hears what came back.
333
333
  sockets$resume_report <- NULL
334
334
  # The documents open in this session (spike/session-documents.R), as the pages
335
335
  # last reported them. A handoff successor starts with its predecessor's list.
336
- sockets$session_documents <- session_documents_decode(Sys.getenv("CARMAR_SESSION_DOCUMENTS"))
337
- Sys.unsetenv("CARMAR_SESSION_DOCUMENTS")
336
+ sockets$session_documents <- session_documents_decode(Sys.getenv("BEATRINA_SESSION_DOCUMENTS"))
337
+ Sys.unsetenv("BEATRINA_SESSION_DOCUMENTS")
338
338
  sockets$worker_recovering <- FALSE
339
339
  sockets$worker_restart_attempts <- 0L
340
340
  sockets$worker_restart_after <- 0
@@ -365,7 +365,7 @@ sockets$terms <- new.env(parent = emptyenv())
365
365
  sockets$term_seq <- 0L
366
366
  # Connection lifetime and R lifetime are separate. By default a session that
367
367
  # has been attached survives closed tabs, browser suspension and lost links.
368
- # Positive CARMAR_LINGER / linger_seconds explicitly opts into idle shutdown.
368
+ # Positive BEATRINA_LINGER / linger_seconds explicitly opts into idle shutdown.
369
369
  # A running task blocks that optional clock; reconnection resets it.
370
370
  # How long /health?hold=1 postpones an explicitly configured idle timeout.
371
371
  # Default retention does not depend on holds or their timing.
@@ -373,8 +373,8 @@ HOLD_GRACE_S <- 30
373
373
  # How long a kernel that has never been connected to waits for its browser
374
374
  # before an unopened launch is cleaned up. This is NOT a session idle timer:
375
375
  # the first connection or hold permanently cancels default boot cleanup.
376
- # Explicit CARMAR_LINGER=0 also disables unopened-launch cleanup for headless use.
377
- BOOT_GRACE_S <- as.numeric(Sys.getenv("CARMAR_BOOT_GRACE", "180"))
376
+ # Explicit BEATRINA_LINGER=0 also disables unopened-launch cleanup for headless use.
377
+ BOOT_GRACE_S <- as.numeric(Sys.getenv("BEATRINA_BOOT_GRACE", "180"))
378
378
  if (!is.finite(BOOT_GRACE_S) || BOOT_GRACE_S < 0) BOOT_GRACE_S <- 180
379
379
  # The same safe default applies locally and behind a proxy. Explicit user or
380
380
  # administrator settings remain authoritative, including an explicit zero.
@@ -398,7 +398,7 @@ linger_s <- resolve_linger()
398
398
  # Only a socket that has beaten at least once is ever reaped — a notebook built
399
399
  # before heartbeats exists on people's disks and must keep today's behaviour
400
400
  # rather than be disconnected while its reader is looking at it.
401
- socket_silence_s <- suppressWarnings(as.numeric(Sys.getenv("CARMAR_SOCKET_SILENCE", "90")))
401
+ socket_silence_s <- suppressWarnings(as.numeric(Sys.getenv("BEATRINA_SOCKET_SILENCE", "90")))
402
402
  if (!length(socket_silence_s) || !is.finite(socket_silence_s)) socket_silence_s <- 90
403
403
 
404
404
  # ── the dispatched-command deadline, PER COMMAND CLASS ─────────────────────
@@ -460,7 +460,7 @@ WORKER_DEADLINES <- c(
460
460
  # Tests need the same branch in seconds rather than minutes. Only a test sets
461
461
  # it; the numbers above ARE the contract.
462
462
  worker_deadline_scale <- suppressWarnings(
463
- as.numeric(Sys.getenv("CARMAR_WORKER_DEADLINE_SCALE", "1")))
463
+ as.numeric(Sys.getenv("BEATRINA_WORKER_DEADLINE_SCALE", "1")))
464
464
  if (!length(worker_deadline_scale) || !is.finite(worker_deadline_scale) ||
465
465
  worker_deadline_scale <= 0) worker_deadline_scale <- 1
466
466
 
@@ -472,14 +472,14 @@ if (!length(worker_deadline_scale) || !is.finite(worker_deadline_scale) ||
472
472
  # than an honest terminal state, because the attach plug is hidden while
473
473
  # recovering and the user cannot reach another session either.
474
474
  worker_respawn_max <- suppressWarnings(
475
- as.integer(Sys.getenv("CARMAR_WORKER_RESPAWN_MAX", "10")))
475
+ as.integer(Sys.getenv("BEATRINA_WORKER_RESPAWN_MAX", "10")))
476
476
  if (!length(worker_respawn_max) || is.na(worker_respawn_max) ||
477
477
  worker_respawn_max < 1L) worker_respawn_max <- 10L
478
478
 
479
479
  # An agent's tool call parked on a page that then died. The page half is the
480
480
  # precise test — a rec no longer in `sockets$open` can never answer — and this
481
481
  # is the backstop for the case where the page is alive and simply never
482
- # replies. It matches the ceiling tools/mcp/carmar-mcp.mjs allows a caller to
482
+ # replies. It matches the ceiling tools/mcp/beatrina-mcp.mjs allows a caller to
483
483
  # ask for (chunk_run's timeout_s caps at 3600), so the supervisor never gives
484
484
  # up on a call the agent is still legitimately waiting for.
485
485
  MCP_PENDING_MAX_S <- 3600
@@ -498,7 +498,7 @@ sockets$worker_queue <- list()
498
498
  # explicitly pairs one page in a loopback consent window; that LOCAL window
499
499
  # owns the socket and relays only messages from the exact approved origin.
500
500
  sockets$pairing_requests <- new.env(parent = emptyenv())
501
- # Consent belongs to this local CarmaR session, not to one popup document.
501
+ # Consent belongs to this local Beatrina session, not to one popup document.
502
502
  # Once an origin is approved, a replacement bridge window for that exact
503
503
  # origin may reconnect without asking again. The environment disappears when
504
504
  # the kernel stops, so approval is still session-scoped.
@@ -536,7 +536,7 @@ sockets$boot_at <- Sys.time()
536
536
 
537
537
  `%||%` <- function(a, b) if (is.null(a)) b else a
538
538
 
539
- #' The page to serve at `/`: the built CarmaR notebook when there is one,
539
+ #' The page to serve at `/`: the built Beatrina notebook when there is one,
540
540
  #' otherwise the bare spike page. The page is PINNED to this kernel's build
541
541
  #' (never the highest version present), `.beta.min` ignored so
542
542
  #' a debuggable build is served during development. The repo keeps builds in
@@ -547,7 +547,7 @@ notebook_page <- function() {
547
547
  # One implementation, in notebook-page.R, because tools/app/launch.sh has to
548
548
  # ask the same question at open time — the announcement below is computed
549
549
  # once at startup and goes stale the moment a new build lands.
550
- page <- carmar_notebook_page(here, CARMAR_KERNEL_BUILD)
550
+ page <- beatrina_notebook_page(here, BEATRINA_KERNEL_BUILD)
551
551
  if (nzchar(page)) return(page)
552
552
  file.path(here, "index.html")
553
553
  }
@@ -559,7 +559,7 @@ notebook_page <- function() {
559
559
  #' does that by comparing ONE string — the boot time exactly as /health
560
560
  #' reports `started`, rendered by the same jsonlite call so the two can never
561
561
  #' differ in their digits. Served pages get it as
562
- #' `<meta name="carmar-session">`; a file page gets the same value in its
562
+ #' `<meta name="beatrina-session">`; a file page gets the same value in its
563
563
  #' fragment from the launcher.
564
564
  session_stamp <- function() {
565
565
  as.character(toJSON(as.numeric(sockets$boot_at), auto_unbox = TRUE))
@@ -574,7 +574,7 @@ session_stamp <- function() {
574
574
  #' @param html the page's text.
575
575
  #' @return the page with the meta in it.
576
576
  stamp_session <- function(html) {
577
- meta <- sprintf('<meta name="carmar-session" content="%s">', session_stamp())
577
+ meta <- sprintf('<meta name="beatrina-session" content="%s">', session_stamp())
578
578
  at <- regexpr("<head\\b[^>]*>", html, perl = TRUE)
579
579
  if (at < 0) at <- regexpr("<html\\b[^>]*>", html, perl = TRUE)
580
580
  if (at < 0) return(paste0(meta, html))
@@ -582,13 +582,13 @@ stamp_session <- function(html) {
582
582
  paste0(substr(html, 1L, cut), meta, substr(html, cut + 1L, nchar(html)))
583
583
  }
584
584
 
585
- #' Where the carmar-mcp stdio server lives on THIS machine, so the setup
585
+ #' Where the beatrina-mcp stdio server lives on THIS machine, so the setup
586
586
  #' dialog can show a command that works verbatim. Repo layout first
587
587
  #' (tools/mcp beside spike/), then a distribution copy beside serve.R.
588
588
  #' Empty when neither exists — the dialog then shows a placeholder path.
589
589
  mcp_server_path <- function() {
590
- for (p in c(file.path(here, "..", "tools", "mcp", "carmar-mcp.mjs"),
591
- file.path(here, "mcp", "carmar-mcp.mjs"))) {
590
+ for (p in c(file.path(here, "..", "tools", "mcp", "beatrina-mcp.mjs"),
591
+ file.path(here, "mcp", "beatrina-mcp.mjs"))) {
592
592
  if (file.exists(p)) return(normalizePath(p))
593
593
  }
594
594
  ""
@@ -607,17 +607,17 @@ find_binary <- function(name, extra = character()) {
607
607
  ""
608
608
  }
609
609
 
610
- #' The Claude Code CLI, if this machine has one. CARMAR_CLAUDE_BIN overrides —
610
+ #' The Claude Code CLI, if this machine has one. BEATRINA_CLAUDE_BIN overrides —
611
611
  #' that is also how tests substitute a deterministic stub.
612
612
  claude_bin <- function() {
613
- override <- Sys.getenv("CARMAR_CLAUDE_BIN", "")
613
+ override <- Sys.getenv("BEATRINA_CLAUDE_BIN", "")
614
614
  if (nzchar(override) && file.exists(override)) return(override)
615
615
  find_binary("claude")
616
616
  }
617
617
 
618
- #' The Codex CLI, if this machine has one. CARMAR_CODEX_BIN overrides (tests).
618
+ #' The Codex CLI, if this machine has one. BEATRINA_CODEX_BIN overrides (tests).
619
619
  codex_bin <- function() {
620
- override <- Sys.getenv("CARMAR_CODEX_BIN", "")
620
+ override <- Sys.getenv("BEATRINA_CODEX_BIN", "")
621
621
  if (nzchar(override) && file.exists(override)) return(override)
622
622
  find_binary("codex")
623
623
  }
@@ -641,11 +641,11 @@ published_origin_valid <- function(origin) {
641
641
  grepl("^https?://[A-Za-z0-9._~-]+(:[0-9]{1,5})?$", origin)
642
642
  }
643
643
 
644
- # A native CarmaR launcher may start the kernel for one published page. The
644
+ # A native Beatrina launcher may start the kernel for one published page. The
645
645
  # URL-scheme handoff is the user's consent gesture; carrying only the exact
646
646
  # origin into this process keeps the permission session-scoped and prevents a
647
647
  # second site from borrowing it.
648
- initial_published_origin <- Sys.getenv("CARMAR_PUBLISHED_ORIGIN", "")
648
+ initial_published_origin <- Sys.getenv("BEATRINA_PUBLISHED_ORIGIN", "")
649
649
  if (published_origin_valid(initial_published_origin)) {
650
650
  sockets$published_approvals[[initial_published_origin]] <- TRUE
651
651
  audit("published-origin-authorized", origin = initial_published_origin,
@@ -670,8 +670,8 @@ if (published_origin_valid(initial_published_origin)) {
670
670
  # looking for one. Any HTML file the user opened could therefore sweep for a
671
671
  # kernel and send `exec` — the same shape as the Chrome extension deleted in
672
672
  # 0.38.0 ("no token and no page identity"), which is documented there as
673
- # unacceptable. A file page cannot hold a secret CarmaR never gave it, and
674
- # CarmaR never sees it open (CarmaR.app claims .qmd/.Rmd/.R/... but not
673
+ # unacceptable. A file page cannot hold a secret Beatrina never gave it, and
674
+ # Beatrina never sees it open (Beatrina.app claims .qmd/.Rmd/.R/... but not
675
675
  # .html, so a double-clicked notebook goes straight to the browser). What is
676
676
  # left is to ASK — once per session, on a local page that names what is
677
677
  # asking. Absent Origin (native same-user clients) is deliberately unchanged:
@@ -681,7 +681,7 @@ if (published_origin_valid(initial_published_origin)) {
681
681
  #' `sample()` draws from the Mersenne Twister, which is seeded from the clock
682
682
  #' and the pid and is trivially reconstructible — fine for shuffling a vector,
683
683
  #' not for a value that decides whether a page may run R. /dev/urandom is the
684
- #' OS CSPRNG on every platform CarmaR ships a kernel for except Windows, where
684
+ #' OS CSPRNG on every platform Beatrina ships a kernel for except Windows, where
685
685
  #' the RNG fallback is documented rather than hidden.
686
686
  secure_token <- function(n = 32L) {
687
687
  alphabet <- c(letters, LETTERS, 0:9)
@@ -701,7 +701,7 @@ secure_token <- function(n = 32L) {
701
701
 
702
702
  FILE_ORIGIN <- "null"
703
703
 
704
- # A notebook page SERVED BY ANOTHER CarmaR KERNEL on this machine. When a
704
+ # A notebook page SERVED BY ANOTHER Beatrina KERNEL on this machine. When a
705
705
  # page's own kernel dies the page stays open (it is HTML the browser already
706
706
  # holds) and can attach to a different session through the badge's plug icon;
707
707
  # it arrives here with the Origin of the kernel that served it,
@@ -721,7 +721,7 @@ local_origin <- function(origin) {
721
721
  # consent click, while a file page that merely found the port cannot.
722
722
  #
723
723
  # serve.R prints the exact file:// URL it expects to be opened, and that URL
724
- # is the one channel CarmaR controls: it already carries `#kernel=<port>`, so
724
+ # is the one channel Beatrina controls: it already carries `#kernel=<port>`, so
725
725
  # it can carry a secret too. The page reads it out of its own fragment and
726
726
  # presents it on the WebSocket URL. A drive-by html file probing 4747..65535
727
727
  # can reach /health and learn a kernel is there, but it was never handed this,
@@ -739,12 +739,12 @@ local_origin <- function(origin) {
739
739
  # so the page can be told where to go BEFORE this process exits. Only the exact
740
740
  # shape secure_token() makes is accepted; anything else is minted afresh.
741
741
  FILE_LAUNCH_CAP <- local({
742
- given <- Sys.getenv("CARMAR_FILE_LAUNCH_CAP", "")
743
- Sys.unsetenv("CARMAR_FILE_LAUNCH_CAP")
742
+ given <- Sys.getenv("BEATRINA_FILE_LAUNCH_CAP", "")
743
+ Sys.unsetenv("BEATRINA_FILE_LAUNCH_CAP")
744
744
  if (grepl("^[A-Za-z0-9]{32}$", given)) given else secure_token(32L)
745
745
  })
746
746
 
747
- open_file_env <- Sys.getenv("CARMAR_OPEN_FILE", "")
747
+ open_file_env <- Sys.getenv("BEATRINA_OPEN_FILE", "")
748
748
  sockets$pending_open <- if (nzchar(open_file_env)) open_file_env else NULL
749
749
  if (!is.null(sockets$pending_open)) audit("open-file-parked")
750
750
 
@@ -783,9 +783,9 @@ pairing_page <- function(origin, nonce = "") {
783
783
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
784
784
  '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; ',
785
785
  "style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'\">",
786
- '<title>', if (is_file) 'Allow this notebook file to use CarmaR?'
787
- else if (is_local) 'Attach this notebook to this CarmaR session?'
788
- else 'Allow this book to use CarmaR?', '</title><style>',
786
+ '<title>', if (is_file) 'Allow this notebook file to use Beatrina?'
787
+ else if (is_local) 'Attach this notebook to this Beatrina session?'
788
+ else 'Allow this book to use Beatrina?', '</title><style>',
789
789
  'body{font:16px/1.5 system-ui,sans-serif;max-width:42rem;margin:10vh auto;padding:0 1.25rem;color:#172033}',
790
790
  '.site{padding:.75rem 1rem;background:#f3f6fb;border-radius:.55rem;overflow-wrap:anywhere}',
791
791
  'button{border:0;border-radius:.5rem;background:#2357d9;color:white;padding:.7rem 1rem;font:inherit;font-weight:650;cursor:pointer}',
@@ -794,15 +794,15 @@ pairing_page <- function(origin, nonce = "") {
794
794
  paste0('<h1>Run this notebook file with your R?</h1>',
795
795
  '<p>A notebook opened from a file on this computer</p>',
796
796
  '<p class="site"><strong>a page opened from disk (file://)</strong></p>',
797
- '<p>wants to use this CarmaR session. The code will run as your user ',
797
+ '<p>wants to use this Beatrina session. The code will run as your user ',
798
798
  'and can access your files and network. A page opened from a file ',
799
799
  'cannot be identified any more exactly than this, so approve it only ',
800
800
  'if you just opened a notebook of your own.</p>')
801
801
  else if (is_local)
802
802
  paste0('<h1>Attach this notebook to this R session?</h1>',
803
- '<p>A CarmaR notebook page served at</p>',
803
+ '<p>A Beatrina notebook page served at</p>',
804
804
  '<p class="site"><strong>', html_escape(origin), '</strong></p>',
805
- '<p>wants to use this CarmaR session instead of its own &mdash; usually ',
805
+ '<p>wants to use this Beatrina session instead of its own &mdash; usually ',
806
806
  'because the session that served it has stopped. It will be treated as ',
807
807
  'your notebook: its chunks run as your user, and it may open a terminal ',
808
808
  'and use your saved AI settings. Approve it only if you just asked a ',
@@ -810,14 +810,14 @@ pairing_page <- function(origin, nonce = "") {
810
810
  else
811
811
  paste0('<h1>Run this book with your R?</h1><p>The published site</p>',
812
812
  '<p class="site"><strong>', html_escape(origin), '</strong></p>',
813
- '<p>wants to send R chunks to this CarmaR session on your computer. ',
813
+ '<p>wants to send R chunks to this Beatrina session on your computer. ',
814
814
  'The code will run as your user and can access your files and network.</p>'),
815
815
  '<form method="get" action="/pair/approve"><input type="hidden" name="challenge" value="',
816
816
  challenge, '"><button type="submit">',
817
817
  if (is_local) 'Attach for this session' else 'Allow for this session',
818
818
  '</button></form>',
819
819
  '<p><small>Nothing runs until you press a chunk\'s Run button. ',
820
- 'Approval disappears when this CarmaR session stops.</small></p></body></html>')
820
+ 'Approval disappears when this Beatrina session stops.</small></p></body></html>')
821
821
  }
822
822
 
823
823
  pairing_bridge_page <- function(rec) {
@@ -829,10 +829,10 @@ pairing_bridge_page <- function(rec) {
829
829
  '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; ',
830
830
  "script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self' ws://127.0.0.1:",
831
831
  port, "; base-uri 'none'\">",
832
- '<title>CarmaR connected</title><style>',
832
+ '<title>Beatrina connected</title><style>',
833
833
  'body{font:16px/1.5 system-ui,sans-serif;max-width:38rem;margin:10vh auto;padding:0 1.25rem;color:#172033}',
834
834
  '.site{overflow-wrap:anywhere;color:#2357d9}</style></head><body>',
835
- '<h1>CarmaR is connected</h1><p>This session now runs chunks pressed on</p>',
835
+ '<h1>Beatrina is connected</h1><p>This session now runs chunks pressed on</p>',
836
836
  '<p class="site"><strong>', html_escape(rec$origin), '</strong></p>',
837
837
  '<p>The page connects by itself and closes this window; if it stays open, you can close it. ',
838
838
  'The approval lasts until this R session stops.</p><script>(function(){',
@@ -841,15 +841,15 @@ pairing_bridge_page <- function(rec) {
841
841
  'Object.assign({type,nonce,origin:target,port:', port, '},extra||{}),target)};',
842
842
  'addEventListener("message",event=>{const data=event.data||{};',
843
843
  'if(event.source!==window.opener||event.origin!==target||data.nonce!==nonce)return;',
844
- 'if(data.type==="carmar:bridge-attach"){const generation=++wsGeneration,old=ws;',
844
+ 'if(data.type==="beatrina:bridge-attach"){const generation=++wsGeneration,old=ws;',
845
845
  'if(old){old.onmessage=old.onerror=old.onclose=null;try{old.close()}catch(e){}}',
846
846
  'ws=new WebSocket(socketUrl);const current=ws;',
847
- 'current.onmessage=message=>{if(generation===wsGeneration)tell("carmar:bridge-frame",{data:message.data})};',
848
- 'current.onerror=()=>{if(generation===wsGeneration)tell("carmar:bridge-error")};',
849
- 'current.onclose=()=>{if(generation===wsGeneration)tell("carmar:bridge-close")};return}',
850
- 'if(data.type==="carmar:bridge-send"&&ws&&ws.readyState===1)ws.send(data.data);',
851
- 'if(data.type==="carmar:bridge-close"&&ws)ws.close()});',
852
- 'tell("carmar:paired");})();</script></body></html>')
847
+ 'current.onmessage=message=>{if(generation===wsGeneration)tell("beatrina:bridge-frame",{data:message.data})};',
848
+ 'current.onerror=()=>{if(generation===wsGeneration)tell("beatrina:bridge-error")};',
849
+ 'current.onclose=()=>{if(generation===wsGeneration)tell("beatrina:bridge-close")};return}',
850
+ 'if(data.type==="beatrina:bridge-send"&&ws&&ws.readyState===1)ws.send(data.data);',
851
+ 'if(data.type==="beatrina:bridge-close"&&ws)ws.close()});',
852
+ 'tell("beatrina:paired");})();</script></body></html>')
853
853
  }
854
854
 
855
855
  pairing_approval_page <- function(challenge) {
@@ -876,14 +876,14 @@ pairing_done_page <- function(rec) {
876
876
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
877
877
  '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; ',
878
878
  "style-src 'unsafe-inline'; base-uri 'none'\">",
879
- '<title>CarmaR attached</title><style>',
879
+ '<title>Beatrina attached</title><style>',
880
880
  'body{font:16px/1.5 system-ui,sans-serif;max-width:38rem;margin:10vh auto;padding:0 1.25rem;color:#172033}',
881
881
  '.site{overflow-wrap:anywhere;color:#2357d9}</style></head><body>',
882
882
  '<h1>Attached</h1><p>The notebook</p>',
883
883
  '<p class="site"><strong>',
884
884
  if (identical(rec$origin, FILE_ORIGIN)) 'a page opened from disk (file://)' else html_escape(rec$origin),
885
885
  '</strong></p>',
886
- '<p>may use this CarmaR session until it stops. It reconnects by itself within ',
886
+ '<p>may use this Beatrina session until it stops. It reconnects by itself within ',
887
887
  'a few seconds; you can close this window.</p></body></html>')
888
888
  }
889
889
 
@@ -915,7 +915,7 @@ host_ok <- function(req) {
915
915
  #'
916
916
  #' Native same-user clients (the app launcher, R, curl, the MCP bridge) do not
917
917
  #' send Origin or Sec-Fetch-Site and remain valid. Browsers do: a foreign page
918
- #' cannot stop a kernel or ask it to open a file, while CarmaR keeps the clean,
918
+ #' cannot stop a kernel or ask it to open a file, while Beatrina keeps the clean,
919
919
  #' stable localhost URL users expect.
920
920
  control_rejection <- function(req, allow_file = FALSE, allow_approved = FALSE) {
921
921
  origin <- req$HTTP_ORIGIN %||% ""
@@ -986,14 +986,14 @@ socket_class <- function(req) {
986
986
  # returns quantiles, level counts and a histogram for ONE named column. It is
987
987
  # strictly narrower than the `view` beside it — same resolution, less surface,
988
988
  # no mutation — which is the bar a new entry has to clear, not "it is useful".
989
- #' The ports of CarmaR kernels that are actually answering right now.
989
+ #' The ports of Beatrina kernels that are actually answering right now.
990
990
  #'
991
991
  #' Read from the runtime directory rather than remembered, so sessions started
992
992
  #' by the R package launcher count too — and health-checked, because a killed
993
993
  #' kernel leaves its file behind and a dead session must not hold a slot.
994
994
  live_session_ports <- function() {
995
- dir <- Sys.getenv("CARMAR_RUNTIME_DIR",
996
- file.path(path.expand("~"), ".carmar", "run"))
995
+ dir <- Sys.getenv("BEATRINA_RUNTIME_DIR",
996
+ file.path(path.expand("~"), ".beatrina", "run"))
997
997
  files <- list.files(dir, pattern = "^kernel-[0-9]+\\.json$", full.names = TRUE)
998
998
  ports <- suppressWarnings(as.integer(sub("^kernel-([0-9]+)\\.json$", "\\1", basename(files))))
999
999
  ports <- ports[!is.na(ports)]
@@ -1037,19 +1037,19 @@ start_sibling_session <- function() {
1037
1037
  if (!(candidate %in% used)) { chosen <- candidate; break }
1038
1038
  }
1039
1039
  if (is.na(chosen)) return(NULL)
1040
- # `chosen` is a PREFERENCE, not a promise. serve.R takes CARMAR_PORT only if
1040
+ # `chosen` is a PREFERENCE, not a promise. serve.R takes BEATRINA_PORT only if
1041
1041
  # the port is still free when IT looks — between our probe and its start the
1042
1042
  # port can go, and it then falls back to a random one. Waiting for the exact
1043
1043
  # port we picked therefore reported failure for a session that had started
1044
1044
  # perfectly well, and left it orphaned. So: remember what was live before,
1045
1045
  # and accept whichever new port appears.
1046
1046
  before <- live_session_ports()
1047
- sib_log <- tryCatch(tempfile("carmar-session-", fileext = ".log"),
1047
+ sib_log <- tryCatch(tempfile("beatrina-session-", fileext = ".log"),
1048
1048
  error = function(e) nullfile())
1049
1049
  started <- tryCatch({
1050
1050
  processx::process$new(file.path(R.home("bin"), "Rscript"),
1051
1051
  c(file.path(here, "serve.R")),
1052
- env = c(Sys.getenv(), CARMAR_PORT = as.character(chosen)),
1052
+ env = c(Sys.getenv(), BEATRINA_PORT = as.character(chosen)),
1053
1053
  # cleanup = FALSE or processx kills this child when
1054
1054
  # `started` is garbage-collected, which is immediately:
1055
1055
  # it is a local that dies with this function. The
@@ -1082,7 +1082,7 @@ start_sibling_session <- function() {
1082
1082
 
1083
1083
  # ── the AI key lives HERE, not in the browser ────────────────────────────────
1084
1084
  #
1085
- # It used to sit in localStorage under `carmar-ai-key-kept`: in the browser's
1085
+ # It used to sit in localStorage under `beatrina-ai-key-kept`: in the browser's
1086
1086
  # storage for this origin, readable by anything that can run script in the page,
1087
1087
  # and one careless export away from being inside a notebook file. A provider key
1088
1088
  # is not notebook content — it is a machine credential — so it belongs where the
@@ -1141,8 +1141,8 @@ AGENT_REFUSED <- c("exec", "interrupt", "force_stop", "restart", "session_upgrad
1141
1141
  # for the same reason FORWARDED and ANALYZE_FORWARDED stay two.
1142
1142
  #
1143
1143
  # Only a notebook page qualifies. A native client is refused even though it
1144
- # could read the 0600 file itself as this user: the point is not that CarmaR
1145
- # can stop it, but that CarmaR does not hand the key over — so the promise
1144
+ # could read the 0600 file itself as this user: the point is not that Beatrina
1145
+ # can stop it, but that Beatrina does not hand the key over — so the promise
1146
1146
  # holds for every agent instead of only the polite ones. A published site is
1147
1147
  # refused because its reader approved it to RUN THE CHUNKS THEY PRESS, not to
1148
1148
  # collect a credential on the way past.
@@ -1173,10 +1173,10 @@ AI_AUDIT_FIELDS <- c("provider", "model", "lane", "tool", "chunk", "decision",
1173
1173
  # The prompt and the response themselves. OFF unless an administrator asks,
1174
1174
  # because a notebook that silently recorded everything its user typed at an AI
1175
1175
  # would be a worse thing to ship than no audit at all — the same judgement
1176
- # CARMAR_LOG itself is off by default for. A compliance deployment turns it on
1176
+ # BEATRINA_LOG itself is off by default for. A compliance deployment turns it on
1177
1177
  # knowingly; a laptop never does.
1178
1178
  AI_AUDIT_TEXT_FIELDS <- c("prompt", "response")
1179
- ai_audit_text <- identical(Sys.getenv("CARMAR_LOG_AI_TEXT", ""), "1")
1179
+ ai_audit_text <- identical(Sys.getenv("BEATRINA_LOG_AI_TEXT", ""), "1")
1180
1180
 
1181
1181
  # How many ai-* records the reviewer export may read back at once.
1182
1182
  AI_AUDIT_READ_MAX <- 5000L
@@ -1192,7 +1192,7 @@ ai_audit_scalar <- function(value, limit) {
1192
1192
  }
1193
1193
 
1194
1194
  ai_key_path <- function() {
1195
- dir <- tools::R_user_dir("carmar", "config")
1195
+ dir <- tools::R_user_dir("beatrina", "config")
1196
1196
  dir.create(dir, recursive = TRUE, showWarnings = FALSE)
1197
1197
  file.path(dir, "ai-key")
1198
1198
  }
@@ -1239,7 +1239,7 @@ ai_key_write <- function(value) {
1239
1239
  # user directory and never in the notebook file, an export, or localStorage —
1240
1240
  # where any local page sharing the file:// origin could read the user's code.
1241
1241
  #
1242
- # Off entirely with CARMAR_NO_HISTORY=1, because "record everything I type"
1242
+ # Off entirely with BEATRINA_NO_HISTORY=1, because "record everything I type"
1243
1243
  # must be refusable.
1244
1244
  HISTORY_MAX <- 2000L
1245
1245
  # Consulted INSIDE history_read()/history_add() per call, never captured at the
@@ -1248,7 +1248,7 @@ HISTORY_MAX <- 2000L
1248
1248
  HISTORY_ENABLED <- isTRUE(settings_value("history_enabled"))
1249
1249
 
1250
1250
  history_path <- function() {
1251
- dir <- tools::R_user_dir("carmar", "data")
1251
+ dir <- tools::R_user_dir("beatrina", "data")
1252
1252
  dir.create(dir, recursive = TRUE, showWarnings = FALSE)
1253
1253
  file.path(dir, "console-history")
1254
1254
  }
@@ -1293,7 +1293,7 @@ history_add <- function(line) {
1293
1293
 
1294
1294
  # ── appearance, across the two origins the notebook is delivered from ────────
1295
1295
  #
1296
- # CarmaR delivers the SAME notebook from a file:// page and from this kernel's
1296
+ # Beatrina delivers the SAME notebook from a file:// page and from this kernel's
1297
1297
  # own http://127.0.0.1:PORT, and browsers partition storage by origin — so a
1298
1298
  # theme chosen at one door is invisible at the other. That is not a theory: it
1299
1299
  # cost an afternoon on 2026-09-10, when an owner's Flat theme turned out to be
@@ -1316,41 +1316,41 @@ history_add <- function(line) {
1316
1316
  # local HTML file shares the file:// origin, so without it `prefs` is a
1317
1317
  # free-form key/value bag any of them could fill.
1318
1318
  #
1319
- # The values travel as the STRINGS localStorage holds. carmar-chunk-custom is
1319
+ # The values travel as the STRINGS localStorage holds. beatrina-chunk-custom is
1320
1320
  # itself JSON; re-parsing it here would make the kernel a party to a format
1321
1321
  # only the page understands, and jsonlite would round its numbers on the way
1322
1322
  # back out — the frame-fidelity lesson, one store further in.
1323
1323
  #
1324
- # Off entirely with CARMAR_NO_PREFS=1, for the same reason CARMAR_NO_HISTORY
1324
+ # Off entirely with BEATRINA_NO_PREFS=1, for the same reason BEATRINA_NO_HISTORY
1325
1325
  # exists: "keep a record of my choices on this machine" must be refusable. It
1326
1326
  # is also what keeps the TEST BATTERY out of the real store — unlike the
1327
1327
  # history, which only fills when somebody types, this publishes on every page
1328
1328
  # connect, so any suite that boots a real page would otherwise seed the user's
1329
1329
  # appearance with whatever its scratch page happened to hold. Set by
1330
1330
  # tools/run-test-suite.mjs for every suite it runs.
1331
- PREFS_ENABLED <- !identical(Sys.getenv("CARMAR_NO_PREFS", ""), "1")
1331
+ PREFS_ENABLED <- !identical(Sys.getenv("BEATRINA_NO_PREFS", ""), "1")
1332
1332
  PREF_MAX_VALUE <- 4096L
1333
1333
  PREF_KEYS <- c(
1334
- "carmar-app-theme", # lib/app-themes.js standard/flat/plain/graphite/onyx
1335
- "carmar-editor-theme", # lib/editor-themes.js one of 37
1336
- "carmar-chunk-style", # lib/chunk-style.js the pre-7.8 key, kept in step
1337
- "carmar-chunk-style-v2", # lib/chunk-style.js the explicit choice
1338
- "carmar-chunk-custom", # lib/chunk-style.js the designer's token overrides
1339
- "carmar-writing-chunk", # lib/writing-chunk.js the Writing surface's own card
1340
- "carmar-run-style", # lib/run-style.js
1341
- "carmar-table-style-v2", # lib/table-render.js
1342
- "carmar-line-numbering", # lib/line-numbering.js
1343
- "carmar-density", # lib/density.js
1344
- "carmar-add-strip-v3", # lib/add-strip.js
1345
- "carmar-soft-wrap", # lib/soft-wrap.js
1346
- "carmar-content-width-v1", # src/notebook-entry.js Standard / Wide / Full
1347
- "carmar-brand-v1", # lib/brand.js the organisation's accent
1348
- "carmar:rich-text", # lib/rtf-editor.js
1349
- "carmar:latex-template" # lib/latex-templates.js
1334
+ "beatrina-app-theme", # lib/app-themes.js standard/flat/plain/graphite/onyx
1335
+ "beatrina-editor-theme", # lib/editor-themes.js one of 37
1336
+ "beatrina-chunk-style", # lib/chunk-style.js the pre-7.8 key, kept in step
1337
+ "beatrina-chunk-style-v2", # lib/chunk-style.js the explicit choice
1338
+ "beatrina-chunk-custom", # lib/chunk-style.js the designer's token overrides
1339
+ "beatrina-writing-chunk", # lib/writing-chunk.js the Writing surface's own card
1340
+ "beatrina-run-style", # lib/run-style.js
1341
+ "beatrina-table-style-v2", # lib/table-render.js
1342
+ "beatrina-line-numbering", # lib/line-numbering.js
1343
+ "beatrina-density", # lib/density.js
1344
+ "beatrina-add-strip-v3", # lib/add-strip.js
1345
+ "beatrina-soft-wrap", # lib/soft-wrap.js
1346
+ "beatrina-content-width-v1", # src/notebook-entry.js Standard / Wide / Full
1347
+ "beatrina-brand-v1", # lib/brand.js the organisation's accent
1348
+ "beatrina:rich-text", # lib/rtf-editor.js
1349
+ "beatrina:latex-template" # lib/latex-templates.js
1350
1350
  )
1351
1351
 
1352
1352
  prefs_path <- function() {
1353
- dir <- tools::R_user_dir("carmar", "data")
1353
+ dir <- tools::R_user_dir("beatrina", "data")
1354
1354
  dir.create(dir, recursive = TRUE, showWarnings = FALSE)
1355
1355
  file.path(dir, "appearance.json")
1356
1356
  }
@@ -1386,7 +1386,7 @@ prefs_write <- function(values) {
1386
1386
  clean <- prefs_clean(values)
1387
1387
  path <- prefs_path()
1388
1388
  # Create, then lock, then write — the order the key and the history beside
1389
- # it use. A theme is not a secret; the point is that everything CarmaR puts
1389
+ # it use. A theme is not a secret; the point is that everything Beatrina puts
1390
1390
  # in the user's data directory is the user's, and judging file by file which
1391
1391
  # ones are harmless is how one of them eventually gets it wrong.
1392
1392
  if (!file.exists(path) && !file.create(path)) return(FALSE)
@@ -1439,7 +1439,7 @@ FORWARDED <- c("env", "obj", "struct", "view", "colstats", "rm", "packages", "do
1439
1439
  # or reaches a process that cannot do it. Adding to EITHER list is a security
1440
1440
  # decision (CLAUDE.md, "FORWARDED in serve.R is an allow-list").
1441
1441
  # The third allow-list. These are answered by THIS process (fileio.R) and never
1442
- # reach the worker's queue, unless CARMAR_FILE_OPS=worker asks for the old
1442
+ # reach the worker's queue, unless BEATRINA_FILE_OPS=worker asks for the old
1443
1443
  # routing. Kept apart from FORWARDED for the reason the first two are apart:
1444
1444
  # "may the browser ask for this?" and "which process answers?" are different
1445
1445
  # questions, and a file op's answer is "the one that is never busy".
@@ -1463,7 +1463,7 @@ SUPERVISOR_JOURNAL_OPS <- c("journal_open", "journal_append", "journal_checkpoin
1463
1463
  # a conversation holds the questions you asked about your data plus the chunk
1464
1464
  # sources those questions carried.
1465
1465
  SUPERVISOR_AI_OPS <- c("ai_list", "ai_read", "ai_write", "ai_delete", "ai_purge", "ai_keys")
1466
- FILE_OPS_IN_WORKER <- identical(Sys.getenv("CARMAR_FILE_OPS", ""), "worker")
1466
+ FILE_OPS_IN_WORKER <- identical(Sys.getenv("BEATRINA_FILE_OPS", ""), "worker")
1467
1467
  # The worker's working directory, as it last reported it (ready, wd and done
1468
1468
  # frames). setwd() in a chunk moves it; a relative path from the page is
1469
1469
  # resolved against THIS, never against the supervisor's own cwd.
@@ -1573,8 +1573,8 @@ app <- list(
1573
1573
  # unauthenticated and CORS-open on purpose.
1574
1574
  r = paste(R.version$major, R.version$minor, sep = "."),
1575
1575
  started = as.numeric(sockets$boot_at),
1576
- protocol = CARMAR_PROTOCOL_VERSION,
1577
- kernel_build = CARMAR_KERNEL_BUILD,
1576
+ protocol = BEATRINA_PROTOCOL_VERSION,
1577
+ kernel_build = BEATRINA_KERNEL_BUILD,
1578
1578
  # What is installed beside this kernel NOW, and
1579
1579
  # how many pages hold it — the two facts an
1580
1580
  # upgrade needs from a running session.
@@ -1591,7 +1591,7 @@ app <- list(
1591
1591
  auto_unbox = TRUE),
1592
1592
  extra = list("Access-Control-Allow-Origin" = "*")))
1593
1593
  }
1594
- # The custom carmar:// handler calls this endpoint as a native same-user
1594
+ # The custom beatrina:// handler calls this endpoint as a native same-user
1595
1595
  # client. Browser requests carry Origin/Sec-Fetch-Site and are refused by
1596
1596
  # control_rejection(), so visiting a site cannot silently grant itself R
1597
1597
  # execution. One launch authorizes one exact origin for this kernel only.
@@ -1618,7 +1618,7 @@ app <- list(
1618
1618
  nonce <- query_param(req$QUERY_STRING, "nonce")
1619
1619
  # FILE_ORIGIN is accepted HERE and only here. `published_origin_valid()`
1620
1620
  # stays strict (`^https?://…`) because it also guards
1621
- # /published/authorize and the CARMAR_PUBLISHED_ORIGIN seed — widening it
1621
+ # /published/authorize and the BEATRINA_PUBLISHED_ORIGIN seed — widening it
1622
1622
  # would let an environment variable or a native launch silently
1623
1623
  # pre-approve every file on disk, which is the opposite of the point.
1624
1624
  if (!identical(origin, FILE_ORIGIN) && !published_origin_valid(origin)) {
@@ -1637,7 +1637,7 @@ app <- list(
1637
1637
  page <- pairing_approval_page(query_param(req$QUERY_STRING, "challenge"))
1638
1638
  if (is.null(page)) {
1639
1639
  audit("pair-rejected", detail = "missing or expired challenge")
1640
- return(resp(400L, "text/plain", "This CarmaR approval request has expired."))
1640
+ return(resp(400L, "text/plain", "This Beatrina approval request has expired."))
1641
1641
  }
1642
1642
  return(resp(200L, "text/html", page))
1643
1643
  }
@@ -1648,12 +1648,12 @@ app <- list(
1648
1648
  blocked <- control_rejection(req, allow_file = TRUE)
1649
1649
  if (!is.null(blocked)) return(blocked)
1650
1650
  audit("shutdown")
1651
- message("CarmaR stopping: explicit Quit request.")
1651
+ message("Beatrina stopping: explicit Quit request.")
1652
1652
  sockets$quit <- TRUE
1653
1653
  return(resp(200L, "application/json",
1654
1654
  toJSON(list(ok = TRUE, stopping = TRUE), auto_unbox = TRUE)))
1655
1655
  }
1656
- # The native door of "Restart into the installed CarmaR" — the menu
1656
+ # The native door of "Restart into the installed Beatrina" — the menu
1657
1657
  # helper's row and the upgrade script use it (launch.sh --restart). Gated
1658
1658
  # exactly like /shutdown; the page's own door is the session_upgrade op.
1659
1659
  if (identical(req$PATH_INFO, "/session/upgrade")) {
@@ -1701,7 +1701,7 @@ app <- list(
1701
1701
  # document reaches a kernel that is ALREADY RUNNING, and it is the reason
1702
1702
  # opening a file no longer costs an R session.
1703
1703
  #
1704
- # The other channel — CARMAR_OPEN_FILE in the supervisor's environment —
1704
+ # The other channel — BEATRINA_OPEN_FILE in the supervisor's environment —
1705
1705
  # is read once at startup, so the only way to deliver a document through
1706
1706
  # it is to start a new process. That is precisely what the launcher used
1707
1707
  # to do: every double-click was a fresh supervisor, worker and analyzer,
@@ -1800,7 +1800,7 @@ app <- list(
1800
1800
  if (identical(method, "OPTIONS")) {
1801
1801
  return(resp(204L, "text/plain", "", extra = c(cors, list(
1802
1802
  "Access-Control-Allow-Methods" = "GET, PUT, OPTIONS",
1803
- "Access-Control-Allow-Headers" = "X-CarmaR-Cap, Content-Type, X-CarmaR-Expected-Bytes",
1803
+ "Access-Control-Allow-Headers" = "X-Beatrina-Cap, Content-Type, X-Beatrina-Expected-Bytes",
1804
1804
  "Access-Control-Max-Age" = "60"))))
1805
1805
  }
1806
1806
  if (identical(method, "GET")) {
@@ -1818,7 +1818,7 @@ app <- list(
1818
1818
  "Content-Length" = as.character(length(bytes))), cors)))
1819
1819
  }
1820
1820
  if (identical(method, "PUT")) {
1821
- grant <- file_cap_take(req$HTTP_X_CARMAR_CAP %||% "", "write")
1821
+ grant <- file_cap_take(req$HTTP_X_BEATRINA_CAP %||% "", "write")
1822
1822
  if (is.null(grant) || !within_root(grant$path)) {
1823
1823
  audit("files-refused", detail = "put")
1824
1824
  return(resp(403L, "text/plain", "no capability for that write", extra = cors))
@@ -1836,7 +1836,7 @@ app <- list(
1836
1836
  }
1837
1837
  expected <- NULL
1838
1838
  if (identical(grant$compare, "utf8-prefix-v1")) {
1839
- prefix <- req$HTTP_X_CARMAR_EXPECTED_BYTES %||% ""
1839
+ prefix <- req$HTTP_X_BEATRINA_EXPECTED_BYTES %||% ""
1840
1840
  n <- suppressWarnings(as.numeric(prefix))
1841
1841
  if (!grepl("^[0-9]+$", prefix) || !is.finite(n) || n > length(body)) {
1842
1842
  return(resp(400L, "text/plain", "missing or invalid expected-content prefix", extra = cors))
@@ -1873,7 +1873,7 @@ app <- list(
1873
1873
  # the Rook environment belongs to the request that opened this socket and
1874
1874
  # cannot change under it.
1875
1875
  rec$class <- socket_class(ws$request)
1876
- # Who the reverse proxy says this is. "" on loopback, always — CarmaR has
1876
+ # Who the reverse proxy says this is. "" on loopback, always — Beatrina has
1877
1877
  # no login of its own and must not grow one here: a header is an identity
1878
1878
  # only for as long as nobody but the proxy can reach the port, which is a
1879
1879
  # property of the deployment, not of this process.
@@ -1991,17 +1991,17 @@ target_page <- function() {
1991
1991
  # the CLI's stream-json lines back to the asking page verbatim, and that is
1992
1992
  # the whole exchange. Credentials are never read, stored, or proxied.
1993
1993
  #
1994
- # The spawned session gets the carmar MCP tools, pinned to THIS kernel via a
1994
+ # The spawned session gets the beatrina MCP tools, pinned to THIS kernel via a
1995
1995
  # 0600 config file (never argv — argv is visible to every local user in ps),
1996
1996
  # and the document tools. The embedded Claude process is a document agent, not
1997
1997
  # a chatbot that prints a draft for the user to move by hand. Inserts and
1998
1998
  # replacements cross the page's revision/contract gateway and remain
1999
1999
  # provisional with Keep/Reject; runs use the ordinary visible chunk path.
2000
2000
  AGENT_CHAT_TOOLS <- paste(
2001
- "mcp__carmar__carmar_status", "mcp__carmar__notebook_read",
2002
- "mcp__carmar__chunk_read", "mcp__carmar__chunk_insert",
2003
- "mcp__carmar__chunk_update", "mcp__carmar__chunk_run",
2004
- "mcp__carmar__file_list", "mcp__carmar__file_read",
2001
+ "mcp__beatrina__beatrina_status", "mcp__beatrina__notebook_read",
2002
+ "mcp__beatrina__chunk_read", "mcp__beatrina__chunk_insert",
2003
+ "mcp__beatrina__chunk_update", "mcp__beatrina__chunk_run",
2004
+ "mcp__beatrina__file_list", "mcp__beatrina__file_read",
2005
2005
  sep = ",")
2006
2006
  MAX_CHATS <- 3L
2007
2007
 
@@ -2056,7 +2056,7 @@ chat_start <- function(rec, cmd) {
2056
2056
  server <- mcp_server_path()
2057
2057
  node <- find_binary("node")
2058
2058
  if (identical(client, "codex")) {
2059
- cfg <- tempfile("carmar-codex-")
2059
+ cfg <- tempfile("beatrina-codex-")
2060
2060
  dir.create(cfg, mode = "0700")
2061
2061
  toml_str <- function(x) as.character(toJSON(x, auto_unbox = TRUE))
2062
2062
  args <- c("exec", "--json", "--ephemeral", "--skip-git-repo-check",
@@ -2071,9 +2071,9 @@ chat_start <- function(rec, cmd) {
2071
2071
  }
2072
2072
  if (!text_only && nzchar(server) && nzchar(node)) {
2073
2073
  args <- c(args,
2074
- "-c", paste0("mcp_servers.carmar.command=", toml_str(node)),
2075
- "-c", paste0("mcp_servers.carmar.args=[", toml_str(server), "]"),
2076
- "-c", paste0("mcp_servers.carmar.env={CARMAR_MCP_URL=", toml_str(paste0(origin_ok, "/")), "}"))
2074
+ "-c", paste0("mcp_servers.beatrina.command=", toml_str(node)),
2075
+ "-c", paste0("mcp_servers.beatrina.args=[", toml_str(server), "]"),
2076
+ "-c", paste0("mcp_servers.beatrina.env={BEATRINA_MCP_URL=", toml_str(paste0(origin_ok, "/")), "}"))
2077
2077
  }
2078
2078
  args <- c(args, "-") # the prompt arrives on stdin
2079
2079
  } else {
@@ -2089,10 +2089,10 @@ chat_start <- function(rec, cmd) {
2089
2089
  args <- c(args, "--tools", "", "--mcp-config", '{"mcpServers":{}}',
2090
2090
  "--strict-mcp-config", "--setting-sources", "")
2091
2091
  } else if (nzchar(server) && nzchar(node)) {
2092
- cfg <- tempfile("carmar-chat-", fileext = ".json")
2093
- writeLines(toJSON(list(mcpServers = list(carmar = list(
2092
+ cfg <- tempfile("beatrina-chat-", fileext = ".json")
2093
+ writeLines(toJSON(list(mcpServers = list(beatrina = list(
2094
2094
  command = node, args = I(server),
2095
- env = list(CARMAR_MCP_URL = paste0(origin_ok, "/"))
2095
+ env = list(BEATRINA_MCP_URL = paste0(origin_ok, "/"))
2096
2096
  ))), auto_unbox = TRUE), cfg)
2097
2097
  Sys.chmod(cfg, mode = "0600")
2098
2098
  args <- c(args, "--mcp-config", cfg, "--strict-mcp-config",
@@ -2177,7 +2177,7 @@ pump_chats <- function() {
2177
2177
  #' Every kernel this user is running, from the runtime records, health-checked.
2178
2178
  #' @return An unnamed list of `list(port, url, title, pid, alive, this)`.
2179
2179
  session_list <- function() {
2180
- dir <- Sys.getenv("CARMAR_RUNTIME_DIR", file.path(path.expand("~"), ".carmar", "run"))
2180
+ dir <- Sys.getenv("BEATRINA_RUNTIME_DIR", file.path(path.expand("~"), ".beatrina", "run"))
2181
2181
  files <- list.files(dir, pattern = "^kernel-[0-9]+\\.json$", full.names = TRUE)
2182
2182
  out <- lapply(files, function(f) {
2183
2183
  rec <- tryCatch(jsonlite::fromJSON(f, simplifyVector = TRUE), error = function(e) NULL)
@@ -2256,7 +2256,7 @@ r_versions <- function() {
2256
2256
  # the MCP plane refuses, wearing a prompt.
2257
2257
  #
2258
2258
  # What the shell can do is what the user can do; this grants nothing new and
2259
- # is audited like every other door. `CARMAR_NO_TERMINAL=1` removes it (a
2259
+ # is audited like every other door. `BEATRINA_NO_TERMINAL=1` removes it (a
2260
2260
  # deployment that must not offer a shell); Windows has no pty here and says so.
2261
2261
  MAX_TERMS <- 4L
2262
2262
  TERM_INPUT_MAX <- 65536L
@@ -2283,11 +2283,11 @@ term_open <- function(rec, cmd) {
2283
2283
  term_send(rec, "", event = "refused", error = why)
2284
2284
  invisible(NULL)
2285
2285
  }
2286
- if (identical(Sys.getenv("CARMAR_NO_TERMINAL"), "1")) {
2287
- return(refuse("The terminal is turned off on this kernel (CARMAR_NO_TERMINAL=1)."))
2286
+ if (identical(Sys.getenv("BEATRINA_NO_TERMINAL"), "1")) {
2287
+ return(refuse("The terminal is turned off on this kernel (BEATRINA_NO_TERMINAL=1)."))
2288
2288
  }
2289
2289
  if (.Platform$OS.type == "windows") {
2290
- return(refuse("The terminal needs a pty, which CarmaR does not provide on Windows yet."))
2290
+ return(refuse("The terminal needs a pty, which Beatrina does not provide on Windows yet."))
2291
2291
  }
2292
2292
  mine <- Filter(function(t) identical(t$rec, rec), mget(ls(sockets$terms), envir = sockets$terms))
2293
2293
  if (length(mine) >= MAX_TERMS) {
@@ -2308,7 +2308,7 @@ term_open <- function(rec, cmd) {
2308
2308
  env = c("current", TERM = "xterm-256color",
2309
2309
  COLORTERM = "truecolor",
2310
2310
  COLUMNS = as.character(cols), LINES = as.character(rows),
2311
- CARMAR_PORT = as.character(port))),
2311
+ BEATRINA_PORT = as.character(port))),
2312
2312
  error = function(e) conditionMessage(e))
2313
2313
  if (is.character(proc)) return(refuse(paste("the shell could not start:", proc)))
2314
2314
  sockets$term_seq <- sockets$term_seq + 1L
@@ -2401,7 +2401,7 @@ JOB_LOG_CHARS <- 1000000L
2401
2401
  # handed the token, and short enough that a token in a stray log is dead by
2402
2402
  # the time anyone reads it. Tests shorten it.
2403
2403
  OUTPUT_CAP_TTL <- local({
2404
- given <- suppressWarnings(as.numeric(Sys.getenv("CARMAR_OUTPUT_CAP_TTL", "")))
2404
+ given <- suppressWarnings(as.numeric(Sys.getenv("BEATRINA_OUTPUT_CAP_TTL", "")))
2405
2405
  if (is.finite(given) && given >= 1 && given <= 3600) given else 60
2406
2406
  })
2407
2407
  OUTPUT_CAP_MAX_BYTES <- 128 * 1024 * 1024
@@ -2439,7 +2439,7 @@ output_cap_sweep <- function() {
2439
2439
  # anything /health discloses; for PUT it travels in a header, never the query.
2440
2440
  FILE_CAP_TTL <- OUTPUT_CAP_TTL
2441
2441
  FILE_UPLOAD_MAX <- local({
2442
- given <- suppressWarnings(as.numeric(Sys.getenv("CARMAR_UPLOAD_MAX", "")))
2442
+ given <- suppressWarnings(as.numeric(Sys.getenv("BEATRINA_UPLOAD_MAX", "")))
2443
2443
  if (is.finite(given) && given >= 1e6 && given <= 2e9) given else 64 * 1024 * 1024
2444
2444
  })
2445
2445
  sockets$file_caps <- list()
@@ -2576,7 +2576,7 @@ job_start <- function(rec, cmd) {
2576
2576
  # settings under both, so a quarto_path set in Settings reaches the render
2577
2577
  # while a job that names its own still wins.
2578
2578
  kernel_start(file.path(here, "job-run.R"), rscript = settings_rscript(),
2579
- env_extra = c(settings_child_env(), spec$env, CARMAR_JOB_ID = id)),
2579
+ env_extra = c(settings_child_env(), spec$env, BEATRINA_JOB_ID = id)),
2580
2580
  error = function(e) conditionMessage(e))
2581
2581
  if (is.character(started)) {
2582
2582
  audit("job-start-failed", detail = started)
@@ -2590,7 +2590,7 @@ job_start <- function(rec, cmd) {
2590
2590
  job$task <- spec$task
2591
2591
  job$root <- spec$root
2592
2592
  job$target <- spec$target
2593
- identity_text <- spec$env["CARMAR_LATEX_IDENTITY"]
2593
+ identity_text <- spec$env["BEATRINA_LATEX_IDENTITY"]
2594
2594
  job$identity <- if (!is.na(identity_text) && nzchar(identity_text)) fromJSON(identity_text, simplifyVector = FALSE) else NULL
2595
2595
  job$output <- NA_character_
2596
2596
  job$title <- job_title(spec)
@@ -2764,7 +2764,7 @@ update_runner_spec <- function(script, args = character(),
2764
2764
  }
2765
2765
 
2766
2766
  update_script_path <- function() {
2767
- script <- trimws(Sys.getenv("CARMAR_UPDATE_SCRIPT", ""))
2767
+ script <- trimws(Sys.getenv("BEATRINA_UPDATE_SCRIPT", ""))
2768
2768
  if (!isTRUE(deployment$loopback) || !nzchar(script) || !file.exists(script)) return("")
2769
2769
  script <- normalizePath(script, winslash = "/", mustWork = TRUE)
2770
2770
  if (is.null(update_runner_spec(script))) return("")
@@ -2807,7 +2807,7 @@ update_job_busy <- function() {
2807
2807
  update_status_state <- function() {
2808
2808
  script <- update_script_path()
2809
2809
  if (!nzchar(script)) return(list(
2810
- supported = FALSE, state = "unavailable", current = CARMAR_KERNEL_BUILD,
2810
+ supported = FALSE, state = "unavailable", current = BEATRINA_KERNEL_BUILD,
2811
2811
  offered = "", previous = "", busy = FALSE, action = "",
2812
2812
  can_check = FALSE, can_update = FALSE, can_defer = FALSE,
2813
2813
  can_rollback = FALSE,
@@ -2816,7 +2816,7 @@ update_status_state <- function() {
2816
2816
 
2817
2817
  busy <- update_job_busy()
2818
2818
  if (busy) return(list(
2819
- supported = TRUE, state = "checking", current = CARMAR_KERNEL_BUILD,
2819
+ supported = TRUE, state = "checking", current = BEATRINA_KERNEL_BUILD,
2820
2820
  offered = "", previous = "", busy = TRUE, action = update_job$action,
2821
2821
  can_check = FALSE, can_update = FALSE, can_defer = FALSE,
2822
2822
  can_rollback = FALSE,
@@ -3099,7 +3099,7 @@ worker_deadline_check <- function() {
3099
3099
  sockets$worker_routes[[wire_id]] else NULL
3100
3100
  reason <- sprintf(
3101
3101
  paste("R did not answer `%s` within %d seconds. The session and its variables are",
3102
- "intact; only this request was abandoned. If it keeps happening, this CarmaR",
3102
+ "intact; only this request was abandoned. If it keeps happening, this Beatrina",
3103
3103
  "may be newer than the R session it is talking to — restart R."),
3104
3104
  type, as.integer(round(limit)))
3105
3105
  audit("worker-deadline", detail = type, id = wire_id,
@@ -3119,7 +3119,7 @@ worker_deadline_check <- function() {
3119
3119
  # ── restart into the installed build ───────────────────────────────────────
3120
3120
  #
3121
3121
  # An upgrade swaps the bundle IN PLACE, so the kernel-version stamp beside
3122
- # this script names the installed build while CARMAR_KERNEL_BUILD names the
3122
+ # this script names the installed build while BEATRINA_KERNEL_BUILD names the
3123
3123
  # one running. The two disagreeing is the one honest signal that a session
3124
3124
  # is stale, and this is the one door through it: start a SUCCESSOR
3125
3125
  # supervisor from the installed files on this same port, tell every page
@@ -3155,13 +3155,13 @@ handoff_refusal <- function(mode = "upgrade", force = FALSE) {
3155
3155
  refuse <- function(error, reason) list(error = error, reason = reason)
3156
3156
  installed <- installed_build()
3157
3157
  if (identical(installed, "unknown")) {
3158
- return(refuse("This kernel cannot tell which CarmaR is installed.", "unknown-install"))
3158
+ return(refuse("This kernel cannot tell which Beatrina is installed.", "unknown-install"))
3159
3159
  }
3160
- if (identical(mode, "upgrade") && identical(installed, CARMAR_KERNEL_BUILD)) {
3161
- return(refuse(sprintf("This session already runs the installed CarmaR %s.", installed), "same-build"))
3160
+ if (identical(mode, "upgrade") && identical(installed, BEATRINA_KERNEL_BUILD)) {
3161
+ return(refuse(sprintf("This session already runs the installed Beatrina %s.", installed), "same-build"))
3162
3162
  }
3163
- if (!nzchar(carmar_notebook_page(here, installed))) {
3164
- return(refuse(sprintf("The installed CarmaR %s has no notebook page beside this kernel.", installed), "no-page"))
3163
+ if (!nzchar(beatrina_notebook_page(here, installed))) {
3164
+ return(refuse(sprintf("The installed Beatrina %s has no notebook page beside this kernel.", installed), "no-page"))
3165
3165
  }
3166
3166
  # Force never spawns a second successor for one port: an attempt that has
3167
3167
  # spawned owns the port until this process exits.
@@ -3180,7 +3180,7 @@ handoff_refusal <- function(mode = "upgrade", force = FALSE) {
3180
3180
  #' kernel, with this port and a capability minted here so it can be told
3181
3181
  #' to the page NOW and honoured by the successor later.
3182
3182
  handoff_page_url <- function(installed, cap) {
3183
- page_file <- normalizePath(carmar_notebook_page(here, installed))
3183
+ page_file <- normalizePath(beatrina_notebook_page(here, installed))
3184
3184
  paste0("file://", utils::URLencode(page_file, reserved = FALSE),
3185
3185
  "#kernel=", port, "&pair=", cap)
3186
3186
  }
@@ -3212,18 +3212,18 @@ session_handoff_begin <- function(mode = "upgrade", force = FALSE, resume = NULL
3212
3212
  sockets$handoff <- list(to = installed, cap = cap, page = page,
3213
3213
  started = Sys.time(), mode = mode, force = force,
3214
3214
  resume = if (keep_token_ok(resume)) resume else NULL)
3215
- audit(audit_name, from = CARMAR_KERNEL_BUILD, to = installed, force = force)
3215
+ audit(audit_name, from = BEATRINA_KERNEL_BUILD, to = installed, force = force)
3216
3216
  # Every page learns where its successor's notebook is — over the gated
3217
3217
  # socket only; the capability never appears in /health. `started` is this
3218
3218
  # kernel's boot time: the page recognises its successor by a LATER one,
3219
3219
  # which is the only test that also works when the build does not change.
3220
- told <- toJSON(list(type = "session-upgrade", from = CARMAR_KERNEL_BUILD,
3220
+ told <- toJSON(list(type = "session-upgrade", from = BEATRINA_KERNEL_BUILD,
3221
3221
  to = installed, page = page, started = as.numeric(sockets$boot_at),
3222
3222
  mode = mode, force = force,
3223
3223
  keep = !is.null(sockets$handoff$resume)), auto_unbox = TRUE)
3224
3224
  lapply(page_recs(), function(r) try(r$ws$send(told), silent = TRUE))
3225
3225
  session_handoff_spawn()
3226
- list(ok = TRUE, from = CARMAR_KERNEL_BUILD, to = installed, page = page,
3226
+ list(ok = TRUE, from = BEATRINA_KERNEL_BUILD, to = installed, page = page,
3227
3227
  started = as.numeric(sockets$boot_at), mode = mode, force = force)
3228
3228
  }
3229
3229
 
@@ -3247,18 +3247,18 @@ session_handoff_spawn <- function() {
3247
3247
  state <- state_dir()
3248
3248
  dir.create(state, recursive = TRUE, showWarnings = FALSE)
3249
3249
  env <- Sys.getenv()
3250
- env[["CARMAR_PORT"]] <- as.character(port)
3251
- env[["CARMAR_WAIT_PORT"]] <- "30"
3250
+ env[["BEATRINA_PORT"]] <- as.character(port)
3251
+ env[["BEATRINA_WAIT_PORT"]] <- "30"
3252
3252
  # A successor that quietly moved to a free port would leave the page
3253
3253
  # polling a dead one for 90 s; strict means it refuses and its log says why.
3254
- env[["CARMAR_PORT_STRICT"]] <- "1"
3255
- env[["CARMAR_FILE_LAUNCH_CAP"]] <- h$cap
3256
- env[["CARMAR_HANDOFF_FROM"]] <- CARMAR_KERNEL_BUILD
3257
- env[["CARMAR_SESSION_TITLE"]] <- runtime_record$title %||% ""
3258
- env[["CARMAR_SESSION_DOCUMENTS"]] <- session_documents_encode(sockets$session_documents)
3254
+ env[["BEATRINA_PORT_STRICT"]] <- "1"
3255
+ env[["BEATRINA_FILE_LAUNCH_CAP"]] <- h$cap
3256
+ env[["BEATRINA_HANDOFF_FROM"]] <- BEATRINA_KERNEL_BUILD
3257
+ env[["BEATRINA_SESSION_TITLE"]] <- runtime_record$title %||% ""
3258
+ env[["BEATRINA_SESSION_DOCUMENTS"]] <- session_documents_encode(sockets$session_documents)
3259
3259
  # Only a token travels, never a path; the successor re-checks it.
3260
- env[["CARMAR_RESUME_TOKEN"]] <- h$resume %||% ""
3261
- if (isTRUE(runtime_record$listen)) env[["CARMAR_LISTEN"]] <- "1"
3260
+ env[["BEATRINA_RESUME_TOKEN"]] <- h$resume %||% ""
3261
+ if (isTRUE(runtime_record$listen)) env[["BEATRINA_LISTEN"]] <- "1"
3262
3262
  # The same R this supervisor runs on, and the serve.R beside THIS file —
3263
3263
  # which, after the in-place swap, is the installed build's.
3264
3264
  rscript <- file.path(R.home("bin"), "Rscript")
@@ -3267,7 +3267,7 @@ session_handoff_spawn <- function() {
3267
3267
  rscript, file.path(here, "serve.R"), env = env,
3268
3268
  stdout = log, stderr = "2>&1", cleanup = FALSE), error = function(e) NULL)
3269
3269
  if (is.null(started) || !started$is_alive()) {
3270
- return(handoff_abandon("The installed CarmaR could not be started."))
3270
+ return(handoff_abandon("The installed Beatrina could not be started."))
3271
3271
  }
3272
3272
  audit("session-upgrade-spawned", to = h$to, pid = started$get_pid(), mode = h$mode %||% "upgrade")
3273
3273
  # Spawn first, kill second: the old worker's whole tree goes down only once
@@ -3563,8 +3563,8 @@ recover_execution_worker <- function() {
3563
3563
  sockets$worker_restart_after <- 0
3564
3564
  sockets$hello <- NULL
3565
3565
  fail_worker_routes(if (isTRUE(sockets$force_stop_asked))
3566
- "R was force-stopped. Its variables and loaded packages are gone; CarmaR is starting a fresh R session."
3567
- else "The R worker stopped unexpectedly. Its session state was lost; CarmaR is starting a fresh R session.")
3566
+ "R was force-stopped. Its variables and loaded packages are gone; Beatrina is starting a fresh R session."
3567
+ else "The R worker stopped unexpectedly. Its session state was lost; Beatrina is starting a fresh R session.")
3568
3568
  rm(list = ls(sockets$running), envir = sockets$running)
3569
3569
  rm(list = ls(sockets$worker_routes), envir = sockets$worker_routes)
3570
3570
  sockets$worker_queue <- list()
@@ -3591,7 +3591,7 @@ recover_execution_worker <- function() {
3591
3591
  sockets$worker_gave_up <- TRUE
3592
3592
  sockets$worker_recovering <- FALSE
3593
3593
  reason <- sprintf(
3594
- paste("R would not start after %d attempts. CarmaR is still running — the notebook,",
3594
+ paste("R would not start after %d attempts. Beatrina is still running — the notebook,",
3595
3595
  "your text and Save all work — but nothing can be run until R is fixed.",
3596
3596
  "A broken .Rprofile or a moved R installation is the usual cause; the kernel",
3597
3597
  "log beside this session has R's own message. Restart R to try again."),
@@ -3759,7 +3759,7 @@ handle_frame <- function(message, rec) {
3759
3759
  }
3760
3760
 
3761
3761
  # ── the MCP plane ─────────────────────────────────────────────────────────
3762
- # A local agent (Claude Code / Codex via tools/mcp/carmar-mcp.mjs) connects
3762
+ # A local agent (Claude Code / Codex via tools/mcp/beatrina-mcp.mjs) connects
3763
3763
  # through the same loopback/Origin/Host gates as a page, then DECLARES itself.
3764
3764
  # From then on it may only ASK (mcp-request, routed to the active notebook
3765
3765
  # window) and use the read-side FORWARDED ops; pages may only ANSWER.
@@ -3782,7 +3782,7 @@ handle_frame <- function(message, rec) {
3782
3782
  return(invisible(NULL))
3783
3783
  }
3784
3784
  if (identical(cmd$type, "open-request")) {
3785
- # The double-clicked document parked by the launcher (CARMAR_OPEN_FILE).
3785
+ # The double-clicked document parked by the launcher (BEATRINA_OPEN_FILE).
3786
3786
  # Pages only — an agent must never consume it — and consume-once: the
3787
3787
  # first page to ask gets the path, everyone after gets "". An empty
3788
3788
  # string, not a JSON null, because jsonlite renders NULL as a truthy {}.
@@ -3807,7 +3807,7 @@ handle_frame <- function(message, rec) {
3807
3807
  audit("mcp-request", id = cmd$id, detail = cmd$tool)
3808
3808
  if (is.null(page)) {
3809
3809
  reply <- toJSON(list(type = "mcp-response", id = cmd$id, ok = FALSE,
3810
- error = "No CarmaR notebook window is connected to this kernel. Open the notebook first."),
3810
+ error = "No Beatrina notebook window is connected to this kernel. Open the notebook first."),
3811
3811
  auto_unbox = TRUE)
3812
3812
  try(rec$ws$send(reply), silent = TRUE)
3813
3813
  return(invisible(NULL))
@@ -3866,7 +3866,7 @@ handle_frame <- function(message, rec) {
3866
3866
  else reply(versions = I(r_versions()), current = current_rscript())
3867
3867
  return(invisible(NULL))
3868
3868
  }
3869
- # "Restart into the installed CarmaR": the page's door. Page-only in both
3869
+ # "Restart into the installed Beatrina": the page's door. Page-only in both
3870
3870
  # senses (AGENT_REFUSED has it too) — a session handoff is the person's
3871
3871
  # decision about their own session, never an agent's.
3872
3872
  if (cmd$type %in% c("session_upgrade", "session_restart") && identical(rec$role, "page")) {
@@ -4201,18 +4201,18 @@ handle_frame <- function(message, rec) {
4201
4201
  "term_close" = "Agents cannot close the user's terminal.",
4202
4202
  "session_list" = "Agents cannot list the user's sessions.",
4203
4203
  "r_versions" = "Agents cannot list or choose R installations.",
4204
- "session_upgrade" = "Agents cannot restart the user's session into another CarmaR.",
4204
+ "session_upgrade" = "Agents cannot restart the user's session into another Beatrina.",
4205
4205
  "session_restart" = "Agents cannot restart the user's session.",
4206
4206
  # settings_GET is refused as firmly as the writes. Not because the
4207
4207
  # values are secret, but because handing back the confinement root,
4208
4208
  # the audit-log path and whether AI-text logging is on is a MAP OF THE
4209
4209
  # DEPLOYMENT'S CONTROLS, given to a process whose entire premise is
4210
4210
  # that it is not the user.
4211
- "settings_get" = "Agents cannot read CarmaR's settings.",
4212
- "settings_set" = "Agents cannot change CarmaR's settings.",
4213
- "settings_reset" = "Agents cannot reset CarmaR's settings.",
4211
+ "settings_get" = "Agents cannot read Beatrina's settings.",
4212
+ "settings_set" = "Agents cannot change Beatrina's settings.",
4213
+ "settings_reset" = "Agents cannot reset Beatrina's settings.",
4214
4214
  "update_status" = "Agents cannot inspect desktop update state.",
4215
- "update_action" = "Agents cannot install, defer, or roll back CarmaR.",
4215
+ "update_action" = "Agents cannot install, defer, or roll back Beatrina.",
4216
4216
  "project_action" = "Agents cannot install or restore project packages.",
4217
4217
  "job_open" = "Agents cannot open files on the user's desktop.",
4218
4218
  "Agents run code through notebook chunks (chunk_run), not raw exec.")
@@ -4382,7 +4382,7 @@ handle_frame <- function(message, rec) {
4382
4382
  # a gate that silently drops the line. reply_err is this handler's own
4383
4383
  # door back to the asking page and carries cmd$id, so the console can
4384
4384
  # attribute the refusal to the line it just sent.
4385
- reply_err("CarmaR could not deliver the answer to R. The question is still open; try again.")
4385
+ reply_err("Beatrina could not deliver the answer to R. The question is still open; try again.")
4386
4386
  return(invisible(NULL))
4387
4387
  }
4388
4388
  sockets$input_waiting <- FALSE
@@ -4413,8 +4413,8 @@ handle_frame <- function(message, rec) {
4413
4413
  action <- if (scalar_chr(cmd$action)) cmd$action else ""
4414
4414
  line <- switch(action,
4415
4415
  continue = "c", over = "n", into = "s", out = "f",
4416
- where = ".carmar_debug_where()",
4417
- abort = 'invokeRestart("carmar_abort_cell")',
4416
+ where = ".beatrina_debug_where()",
4417
+ abort = 'invokeRestart("beatrina_abort_cell")',
4418
4418
  eval = if (scalar_chr(cmd$expr)) cmd$expr else NULL)
4419
4419
  if (is.null(line) || grepl("[\n\r]", line) || nchar(line) > 4000L) {
4420
4420
  reply_err("bad debug command")
@@ -4427,7 +4427,7 @@ handle_frame <- function(message, rec) {
4427
4427
  # end of the debuggable code the where line reaches the dispatch loop,
4428
4428
  # which ignores what it cannot parse — harmless by construction.
4429
4429
  if (action %in% c("over", "into", "out")) {
4430
- try(kernel_console(k, ".carmar_debug_where()"), silent = TRUE)
4430
+ try(kernel_console(k, ".beatrina_debug_where()"), silent = TRUE)
4431
4431
  }
4432
4432
  return(invisible(NULL))
4433
4433
  }
@@ -4507,12 +4507,12 @@ handle_frame <- function(message, rec) {
4507
4507
  return(invisible(NULL))
4508
4508
  }
4509
4509
  # ONE OF THE TWO DOORS THE SUPERVISOR ACTUALLY OWNS. A policy that permits
4510
- # no key-based provider means CarmaR does not keep a provider key at all:
4510
+ # no key-based provider means Beatrina does not keep a provider key at all:
4511
4511
  # not handed out, not stored. The user can still paste one into the tab —
4512
4512
  # this is their own browser and no kernel can prevent that — but nothing
4513
4513
  # persists it for them and the attempt is in the audit log. The refusal
4514
- # covers `set` as much as `get`, because a key CarmaR holds under a
4515
- # local-only policy is a key CarmaR should never have accepted.
4514
+ # covers `set` as much as `get`, because a key Beatrina holds under a
4515
+ # local-only policy is a key Beatrina should never have accepted.
4516
4516
  if (isTRUE(ai_policy$set) && !isTRUE(ai_policy$allows_key)) {
4517
4517
  audit("ai-key-refused", reason = "policy", class = rec$class %||% "unknown")
4518
4518
  reply(error = ai_policy_reason(ai_policy), policy = TRUE)
@@ -4742,7 +4742,7 @@ handle_frame <- function(message, rec) {
4742
4742
  }
4743
4743
  # csl_place: the style beside the document, as a .bib sits beside a
4744
4744
  # .tex — what makes `csl: apa.csl` in the front matter render the same
4745
- # style with no CarmaR present. A dependent style's parent goes too, or
4745
+ # style with no Beatrina present. A dependent style's parent goes too, or
4746
4746
  # pandoc would go looking for it. Never overwrites: a different file of
4747
4747
  # that name beside the document is the author's, and is reported.
4748
4748
  style <- csl_resolve(here, plugin_policy, cmd$name)
@@ -4770,7 +4770,7 @@ handle_frame <- function(message, rec) {
4770
4770
  # The AI conversation, into the audit stream. Fire-and-forget: there is no
4771
4771
  # reply, because a turn must never wait on its own logging, and a page that
4772
4772
  # could tell whether the write landed would be a probe for whether auditing
4773
- # is on. `audit()` is already a no-op without CARMAR_LOG, so an ordinary
4773
+ # is on. `audit()` is already a no-op without BEATRINA_LOG, so an ordinary
4774
4774
  # laptop pays nothing here beyond the parse it already did.
4775
4775
  if (identical(cmd$type, "ai-audit")) {
4776
4776
  event <- ai_audit_scalar(cmd$event, 32L)
@@ -4799,7 +4799,7 @@ handle_frame <- function(message, rec) {
4799
4799
  # Reading the audit stream BACK, for the reviewer export.
4800
4800
  #
4801
4801
  # Page-only on the same grounds as the console history, and more so: with
4802
- # CARMAR_LOG_AI_TEXT on, these lines contain the prompts the user typed. A
4802
+ # BEATRINA_LOG_AI_TEXT on, these lines contain the prompts the user typed. A
4803
4803
  # declared agent is refused (AGENT_REFUSED); a published origin and a native
4804
4804
  # client are refused by class.
4805
4805
  #
@@ -4869,7 +4869,7 @@ handle_frame <- function(message, rec) {
4869
4869
 
4870
4870
  # Appearance carried between the two origins the notebook is delivered from
4871
4871
  # (see PREF_KEYS). Page-only on the console-history rule exactly: this is the
4872
- # user's own machine state, kept for them because CarmaR put it out of reach
4872
+ # user's own machine state, kept for them because Beatrina put it out of reach
4873
4873
  # of the origin they opened it at. A declared agent has no appearance.
4874
4874
  if (identical(cmd$type, "prefs")) {
4875
4875
  if (!scalar_chr(cmd$id)) return(invisible(NULL))
@@ -4915,7 +4915,7 @@ handle_frame <- function(message, rec) {
4915
4915
  # same reason as ai-key: the supervisor answers it itself.
4916
4916
  #
4917
4917
  # Page-only for the ai-key reason exactly: a native client could edit
4918
- # ~/.config/R/carmar/settings.json itself as this user, but CarmaR does not
4918
+ # ~/.config/R/beatrina/settings.json itself as this user, but Beatrina does not
4919
4919
  # do it for it, so the promise holds for every client rather than only the
4920
4920
  # polite ones. A published origin is refused because its reader approved that
4921
4921
  # site to run the chunks they press, not to reconfigure their kernel.
@@ -4927,7 +4927,7 @@ handle_frame <- function(message, rec) {
4927
4927
  audit("settings-refused", reason = "class", detail = cmd$type,
4928
4928
  class = rec$class %||% "unknown")
4929
4929
  reply(ok = FALSE, reason = "class",
4930
- error = "Only the notebook page may read or change CarmaR's settings.")
4930
+ error = "Only the notebook page may read or change Beatrina's settings.")
4931
4931
  return(invisible(NULL))
4932
4932
  }
4933
4933
  # An unauthenticated shared kernel has no "the user" whose home directory
@@ -4974,7 +4974,7 @@ handle_frame <- function(message, rec) {
4974
4974
  # because those are precisely the ones that can be honoured now, and
4975
4975
  # reporting a new linger while running on the old one is the lie the
4976
4976
  # whole `effect` field exists to prevent.
4977
- settings_state <<- carmar_settings_resolve()
4977
+ settings_state <<- beatrina_settings_resolve()
4978
4978
  linger_s <<- resolve_linger()
4979
4979
  HISTORY_ENABLED <<- isTRUE(settings_value("history_enabled"))
4980
4980
  audit("settings", detail = "get", status = settings_state$file$status)
@@ -4983,8 +4983,8 @@ handle_frame <- function(message, rec) {
4983
4983
  }
4984
4984
 
4985
4985
  if (identical(cmd$type, "settings_reset")) {
4986
- unlink(carmar_settings_path())
4987
- settings_state <<- carmar_settings_resolve()
4986
+ unlink(beatrina_settings_path())
4987
+ settings_state <<- beatrina_settings_resolve()
4988
4988
  linger_s <<- resolve_linger()
4989
4989
  HISTORY_ENABLED <<- isTRUE(settings_value("history_enabled"))
4990
4990
  audit("settings", detail = "reset", ok = TRUE)
@@ -5024,7 +5024,7 @@ handle_frame <- function(message, rec) {
5024
5024
  if (is.null(cmd$value)) {
5025
5025
  values[[key]] <- NULL # unset: back to env/default
5026
5026
  } else {
5027
- verdict <- carmar_settings_validate(key, cmd$value)
5027
+ verdict <- beatrina_settings_validate(key, cmd$value)
5028
5028
  if (!isTRUE(verdict$ok)) {
5029
5029
  # Refused, never coerced, and the file is NOT written — the ai-key
5030
5030
  # scar, where a payload of the wrong type fell through to "" and
@@ -5038,13 +5038,13 @@ handle_frame <- function(message, rec) {
5038
5038
  values[[key]] <- verdict$value
5039
5039
  }
5040
5040
 
5041
- if (!isTRUE(carmar_settings_write(values))) {
5041
+ if (!isTRUE(beatrina_settings_write(values))) {
5042
5042
  audit("settings", detail = "set", key = key, ok = FALSE)
5043
5043
  reply(ok = FALSE, key = key, reason = "write",
5044
5044
  error = "The settings file could not be written.")
5045
5045
  return(invisible(NULL))
5046
5046
  }
5047
- settings_state <<- carmar_settings_resolve()
5047
+ settings_state <<- beatrina_settings_resolve()
5048
5048
  applied <- FALSE
5049
5049
  if (identical(entry$effect, "live")) {
5050
5050
  if (identical(key, "linger_seconds")) {
@@ -5248,8 +5248,8 @@ pump <- function() {
5248
5248
  # Session-wide lifecycle frames have no owner and must reach every tab —
5249
5249
  # and no id to rewrite, so they forward exactly as the kernel wrote them.
5250
5250
  if (identical(e$type, "ready")) {
5251
- e$protocol <- CARMAR_PROTOCOL_VERSION
5252
- e$kernel_build <- CARMAR_KERNEL_BUILD
5251
+ e$protocol <- BEATRINA_PROTOCOL_VERSION
5252
+ e$kernel_build <- BEATRINA_KERNEL_BUILD
5253
5253
  e$installed_build <- installed_build()
5254
5254
  # session_upgrade is the SUPERVISOR's verb; it joins the worker's
5255
5255
  # vocabulary here so a page can tell, without probing, that this
@@ -5296,7 +5296,7 @@ if (deployment$require_origin) {
5296
5296
  # first line. An operator who has bound to the network gets the posture
5297
5297
  # stated back to them, because the two spellings differ enormously and the
5298
5298
  # difference is invisible from the outside.
5299
- message("CarmaR listening on ", host, ":", port,
5299
+ message("Beatrina listening on ", host, ":", port,
5300
5300
  " — strict: a client with no Origin is refused")
5301
5301
  message(if (deployment$trust_proxy)
5302
5302
  paste0(" identity: ", deployment$user_header, " from ",
@@ -5327,35 +5327,35 @@ file_url <- notebook_file_url(notebook_file, port, FILE_LAUNCH_CAP)
5327
5327
 
5328
5328
  #' Discovery for local agents — the Jupyter runtime-dir pattern.
5329
5329
  #'
5330
- #' `tools/mcp/carmar-mcp.mjs` (the stdio server Claude Code / Codex spawn) has
5330
+ #' `tools/mcp/beatrina-mcp.mjs` (the stdio server Claude Code / Codex spawn) has
5331
5331
  #' no stdout to read the URL from, so the kernel writes one small file a local
5332
- #' process owned by the SAME user can find: ~/.carmar/run/kernel-<port>.json,
5332
+ #' process owned by the SAME user can find: ~/.beatrina/run/kernel-<port>.json,
5333
5333
  #' mode 0600. This is discovery metadata, not a browser credential. A same-user
5334
5334
  #' local process was never outside the trust line — it could already exec
5335
5335
  #' anything. Removed on clean shutdown;
5336
5336
  #' consumers must health-check before trusting a file (kills leave litter).
5337
- #' CARMAR_RUNTIME_DIR relocates it (tests use a scratch dir);
5338
- #' CARMAR_NO_RUNTIME_FILE=1 turns it off.
5337
+ #' BEATRINA_RUNTIME_DIR relocates it (tests use a scratch dir);
5338
+ #' BEATRINA_NO_RUNTIME_FILE=1 turns it off.
5339
5339
  runtime_file <- ""
5340
5340
  runtime_record <- list(url = url, file = file_url, port = port, pid = Sys.getpid(),
5341
5341
  started = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z"))
5342
5342
  # A successor started by a handoff keeps the session's name, so the menu
5343
5343
  # row does not flicker to "Untitled" between the two processes.
5344
5344
  local({
5345
- title <- Sys.getenv("CARMAR_SESSION_TITLE", "")
5346
- Sys.unsetenv("CARMAR_SESSION_TITLE")
5345
+ title <- Sys.getenv("BEATRINA_SESSION_TITLE", "")
5346
+ Sys.unsetenv("BEATRINA_SESSION_TITLE")
5347
5347
  title <- trimws(substr(gsub("[\"\\\\]", "'", gsub("[[:cntrl:]]", " ", title)), 1L, 120L))
5348
5348
  if (nzchar(title)) runtime_record$title <<- title
5349
5349
  label <- session_documents_label(sockets$session_documents)
5350
5350
  if (nzchar(label)) runtime_record$documents <<- label
5351
5351
  })
5352
- # CARMAR_LISTEN=1 marks a kernel started headless for published pages (the
5352
+ # BEATRINA_LISTEN=1 marks a kernel started headless for published pages (the
5353
5353
  # menu's Listen for Web Pages, the keep-ready daemon). The menu helper shows
5354
5354
  # such a kernel as its Listen checkbox, not as a session row — there is no
5355
5355
  # document behind it to name. The mark is HOW IT STARTED, so the first page
5356
5356
  # that actually attaches clears it (see the upgrade handler): from then on it
5357
5357
  # is an ordinary session.
5358
- if (identical(Sys.getenv("CARMAR_LISTEN"), "1")) runtime_record$listen <- TRUE
5358
+ if (identical(Sys.getenv("BEATRINA_LISTEN"), "1")) runtime_record$listen <- TRUE
5359
5359
  # The failsafe's session is listed by `beatrina status` and stopped by `beatrina stop` like any other:
5360
5360
  # the same app, a host name, the boot instant /health reports, and the document it opened.
5361
5361
  if (nzchar(FAILSAFE_REASON)) {
@@ -5412,9 +5412,9 @@ clear_runtime_listen <- function() {
5412
5412
  write_runtime()
5413
5413
  }
5414
5414
 
5415
- if (!identical(Sys.getenv("CARMAR_NO_RUNTIME_FILE"), "1")) {
5416
- runtime_dir <- Sys.getenv("CARMAR_RUNTIME_DIR",
5417
- file.path(path.expand("~"), ".carmar", "run"))
5415
+ if (!identical(Sys.getenv("BEATRINA_NO_RUNTIME_FILE"), "1")) {
5416
+ runtime_dir <- Sys.getenv("BEATRINA_RUNTIME_DIR",
5417
+ file.path(path.expand("~"), ".beatrina", "run"))
5418
5418
  made <- dir.exists(runtime_dir) ||
5419
5419
  dir.create(runtime_dir, recursive = TRUE, showWarnings = FALSE)
5420
5420
  if (made) {
@@ -5423,7 +5423,7 @@ if (!identical(Sys.getenv("CARMAR_NO_RUNTIME_FILE"), "1")) {
5423
5423
  }
5424
5424
  }
5425
5425
 
5426
- audit("started", port = port, r = R.version.string, root = Sys.getenv("CARMAR_ROOT", ""))
5426
+ audit("started", port = port, r = R.version.string, root = Sys.getenv("BEATRINA_ROOT", ""))
5427
5427
  cat(toJSON(list(url = url, file = file_url), auto_unbox = TRUE), "\n")
5428
5428
  flush(stdout())
5429
5429
 
@@ -5504,7 +5504,7 @@ mcp_pending_sweep <- function() {
5504
5504
  as.numeric(difftime(now, entry$at, units = "secs")) >= MCP_PENDING_MAX_S
5505
5505
  if (!page_gone && !stale) next
5506
5506
  why <- if (page_gone)
5507
- "The CarmaR notebook window that was answering this request closed before it replied."
5507
+ "The Beatrina notebook window that was answering this request closed before it replied."
5508
5508
  else sprintf("The notebook did not answer within %d seconds.",
5509
5509
  as.integer(MCP_PENDING_MAX_S))
5510
5510
  audit("mcp-abandoned", id = id, reason = if (page_gone) "page closed" else "timeout")
@@ -5554,7 +5554,7 @@ linger_check <- function() {
5554
5554
  if (as.numeric(difftime(now, sockets$idle_since, units = "secs")) >= grace) {
5555
5555
  reason <- if (unopened_default) "unopened-launch" else "configured-idle-timeout"
5556
5556
  audit("linger-shutdown", grace = grace, reason = reason)
5557
- message(sprintf("CarmaR stopping: %s (grace %s seconds).", reason, grace))
5557
+ message(sprintf("Beatrina stopping: %s (grace %s seconds).", reason, grace))
5558
5558
  sockets$quit <- TRUE
5559
5559
  }
5560
5560
  invisible(NULL)