This document describes packaging the Python venv compute bridge and its scientific productivity surfaces as a stable core extension (LibrePy), while WriterAgent keeps the fast-moving AI features (chat, tools, MCP, grammar, embeddings, and so on).
It answers two questions:
- Which
plugin/framework/modules must ship (entire files, even when only one function is used)? - What is the complete file list for the feature bundle (same whole-file rule)?
For user-facing behavior (=PYTHON() / =PY(), Run Python Script, domain helpers, Monaco, OCR, TeX), see Enabling NumPy & Python in LibreOffice. This doc is about packaging and dependencies, not tutorials.
Live QA (LibrePy functionality, either OXT): librepy-manual-qa-plan.md. Same =PY() / venv / RPS code in WriterAgent; the plan does not require installing LibrePy.oxt.
Build / install targets and the prototype tree live under Prototype extension.
LibrePy.oxt is a working standalone extension in this repo — not just a paper design.
| Item | Status |
|---|---|
make build-core → build/LibrePy.oxt |
Shipped — scripts/build_librepy_oxt.py + scripts/librepy_bundle_paths.py |
make deploy-core / register-librepy-oxt |
Shipped — installs org.extension.librepy; removes WriterAgent (org.extension.writeragent) so only one OXT is active |
| Extension identity | Shipped — extension-core/ (description.xml, Addons.xcu, Jobs.xcu, ProtocolHandler.xcu, CalcAddIns, XPythonFunction.rdb) |
| Bootstrap | Shipped — plugin/main_core.py, Python-only Settings (plugin/librepy/settings.py) |
| Weekly update check | Shipped — same helper as WriterAgent (extension_update_check.py); feed update-librepy.xml; scheduled from LibrePy StartupJob after init_logging |
| Layers 0–6 feature set | Shipped in LibrePy.oxt — =PY() / =PYTHON(), warm venv, Run Python Script, Reset Session, Monaco, domain helpers, Vision/OCR, TeX/Math (see Feature bundles); user guide: ../enabling_numpy_in_libreoffice.md |
| Filtered locales / slim vendor | Shipped — make compile-translations-core (ast + polib; no GNU gettext); vendor limited to isodate + json_repair + latex2mathml |
xl Calc-parity helpers |
Deferred — excluded from LibrePy (§ below) |
WriterAgent AI overlay on top of LibrePy (both OXTs installed, shared plugin/ via extend_path) |
Not shipped — goal in Coexistence / prototype §7; today install only one OXT at a time |
WriterAgent stripped of duplicate =PY() / menus |
Not shipped — full WriterAgent OXT still bundles its own Python stack |
plugin/__init__.py already uses pkgutil.extend_path (useful for other dual-OXT pairs such as WriterAgent + LibreHarper). LibrePy × WriterAgent side-by-side ownership of =PY() is still future work.
Aligned with ../enabling_numpy_in_libreoffice.md and linked topic docs.
| In core extension | WriterAgent extension only |
|---|---|
=PYTHON() / =PY() Calc add-in + warm venv worker |
=PROMPT(), chat sidebar, MCP, grammar |
Run Python Script…, document scripts, init script, Reset Python Session, wa.scripts / wa.doc named libraries (named_scripts.py; host fetch via host_rpc, not writeragent_api) |
Chat tools (run_venv_python_script, analyze_data, extract_structure_from_image, …) |
| Monaco (Edit Python in Cell…, Run Python Script editor) | Analysis Sub-Agent, tool loop, LLM client |
| LibrePy Python sidebar (Calc deck: cells + diagnostics; also in WriterAgent.oxt) | WriterAgent chat deck (WriterAgentDeck) |
| NumPy domain trusted helpers (Analysis, Viz, Symbolic, Units, Forecast, Optimize, Quant, Text Analytics menu) | Embeddings (plugin/embeddings/, folder FTS, hybrid search) |
| Vision/OCR (Run Python Script Vision Helpers + settings) | DuckDB SQL (domain=sql, spreadsheet SQL helpers) |
| TeX/Math (Insert LaTeX Math + Writer HTML math in Run Python Script egress) | Calc spreadsheet → Python import (convert_spreadsheet_to_python) |
Jupyter (File → Open .ipynb → Writer + ▶ cells) |
— |
Confirmed: Core ships menu + formula surfaces plus Writer File → Open .ipynb. Chat tool wrappers stay in WriterAgent even when they call the same trusted compute modules.
Explicit exclusions (do not register menus, trusted domains, or probe groups for these in a core OXT):
- Embeddings —
plugin/embeddings/,WORKER_POOL_EMBEDDINGS,embeddings_*/embedding/langdetecttrusted domains - DuckDB —
plugin/scripting/venv/duckdb_sql.py,SCRIPT_ORIGIN_SQL,domain=sql - Spreadsheet import —
plugin/calc/spreadsheet_import/(proposed),calc.convert_spreadsheet_to_python - Calc-parity
xlhelpers — not in LibrePy today; see Calc-parityxlhelpers (deferred) below.
WriterAgent ships plugin/scripting/calc_functions.py and plugin/scripting/venv/calc_functions_*.py — 259 Calc/Excel formula parity helpers auto-imported as calc in the venv (e.g. calc.sumif(...), calc.xlookup(...)). They were built so the prototype spreadsheet import could emit compact Python instead of pasting inline def blocks per workbook (../calc/spreadsheet-to-python-import.md, low priority). This is not the same as Microsoft Python in Excel’s xl() range bridge (comparison).
LibrePy (core OXT) excludes them for now (~134 KB source; filtered in scripts/librepy_bundle_paths.py via LIBREPY_CALC_FUNCTIONS_EXCLUDES). Rationale:
- Minimal core first — Layers 0–6 should prove
=PY(), Run Python Script, domain helpers, Monaco, Vision, and TeX before adding spreadsheet-conversion surface area. - Spreadsheet import is WriterAgent-only — menu, translator, and chat tool are not in the core bundle; nothing in LibrePy menus calls
calc.*today. - Coverage not fully exercised in core QA — parity tests live in WriterAgent (
tests/scripting/test_calc_functions.py); bundling without a core conversion workflow would ship dead weight for most installs.
What LibrePy still ships: calc_functions_common.py — host-side name frozensets for Analysis, Viz, Forecast, etc. (no NumPy on the LO host).
Runtime without the library: inject_auto_imports skips calc when the module is absent; =PY() and Run Python Script work with np/pd and domain helpers. Only calc.* in user scripts or converted formulas would fail.
Likely re-include later when spreadsheet conversion moves into core and parity is validated — remove LIBREPY_CALC_FUNCTIONS_EXCLUDES from the bundle filter; no refactor required. Power users may also want calc.* in =PY() cells even before the conversion menu ships; that is a reasonable follow-on once tests and docs catch up.
| Concern | Core extension | WriterAgent extension |
|---|---|---|
| Change rate | Low: Calc add-in, subprocess protocol, trusted helpers, Monaco | High: models, tools, UI, MCP |
| Stability | Suitable to ship with LibreOffice core | Third-party / frequent releases |
| Scope | =PY(), scientific menus, Monaco, OCR, TeX |
Chat, =PROMPT(), embeddings, duckdb, jupyter, grammar |
WriterAgent registers =PYTHON() / =PY() and =PROMPT() as separate UNO components (addin.py, prompt_addin.py). LibrePy registers only the Python add-in via addin_librepy.py and must not register =PROMPT().
The full core OXT is the union of all layers below. Each layer adds whole files; later layers depend on earlier ones.
flowchart TB
L0["Layer 0: =PYTHON core"]
L1["Layer 1: Trusted RPC + venv compute"]
L2["Layer 2: Run Python Script + egress"]
L3["Layer 3: NumPy domain helpers"]
L4["Layer 4: Monaco editor"]
L5["Layer 5: Vision/OCR"]
L6["Layer 6: TeX/Math import"]
L0 --> L1 --> L2 --> L3
L2 --> L4
L3 --> L5
L2 --> L6
| Layer | Enables | Detail section |
|---|---|---|
| 0 | =PYTHON() / =PY() formula |
Layer 0 |
| 1 | Trusted helper RPC (required for layers 3–5) | Layer 1 |
| 2 | Run Python Script…, Reset Session, document scripts | Layer 2 |
| 3 | Analysis, Viz, Symbolic, Units, Forecast, Optimize, Quant, Text Analytics | Appendix E |
| 4 | Edit Python in Cell…, Monaco Run/Save | Appendix F |
| 5 | Vision Helpers, Vision OCR Settings | Appendix G |
| 6 | Insert LaTeX Math…, HTML math in Writer insert | Appendix D |
LibreOffice’s embedded Python must not import NumPy/pandas from arbitrary user installs (ABI mismatch → crash). User Python runs in a separate venv interpreter over length-prefixed Pickle5 frames. Trusted helpers use the same warm child via action: "run_trusted_action" (no AST sandbox inside reviewed modules).
flowchart TB
subgraph loHost [LibreOffice host process]
PY["=PY(code, data?)"]
RPS[Run Python Script menu]
PF[python/function.py]
PR[python_runner.py]
TR[trusted_rpc.py]
VW[venv_worker.py]
CFG[framework.config]
end
subgraph child [User venv subprocess]
WH[venv/worker_harness.py]
TD[venv/trusted_dispatch.py]
VS[venv/venv_sandbox.py]
LPE[local_python_executor]
DOM[venv/analysis viz symbolic ...]
end
PY --> PF --> VW
RPS --> PR --> VW
PR --> TR --> VW
VW --> CFG
VW -.->|stdin/stdout Pickle5| WH
WH --> VS --> LPE
WH --> TD --> DOM
Recalc constraint: =PYTHON() runs synchronously during Calc recalc. The implementation deliberately avoids UI event pumping on this path (see comments in function.py) so the formula engine is not re-entered.
Config keys (from plugin/scripting/module.yaml):
| Key | Role |
|---|---|
scripting.python_venv_path |
User venv directory; empty → sys.executable (LO embedded Python, stdlib-only unless extras installed there) |
scripting.python_session_mode |
isolated (default) or shared (workbook namespace for =PY() cells) |
scripting.python_exec_timeout |
Wall-clock seconds per user script run (default 10, clamp 1–600) |
scripting.python_auto_spill |
Auto-spill list/DataFrame returns from single-cell =PY() |
Stored in writeragent.json today (plugin/framework/config.py). A core extension would choose whether to reuse that file, use a dedicated JSON name, or bind LO Tools → Options.
IPC detail: numpy-serialization.md.
Python loads whole modules. If a file is imported, the entire file ships in the OXT, even if only one function is called.
Call chain:
PythonFunction.python() → execute_python_addin → calc_addin_data / payload_codec → run_code_in_user_venv → PythonWorkerManager → venv/worker_harness → venv/venv_sandbox → LocalPythonExecutor
Error display: function.py uses framework.errors.format_error_payload plus a local _format_error_for_display so =PY() does not import plugin.framework.client (package init is now lazy HTTP/errors only, but the add-in still stays off that package).
Config: get_config_str("scripting.python_venv_path"), session mode, timeout, auto-spill.
This closure does not call the LLM, chat panel, or MCP.
=PY()(WriterAgent): registeraddin.py→function.py(noLlmClient).=PY()(LibrePy): registeraddin_librepy.py→ samefunction.py.=PROMPT(): registerprompt_addin.py→ loadsprompt_function.py(LLM stack). WriterAgent only.
A core OXT must not register prompt_addin.py / prompt_function.py. See Recommended refactors.
| File | Why it ships |
|---|---|
plugin/framework/config.py |
Config path, cache, JSON I/O, typed getters |
plugin/framework/config_schema.py |
WriterAgentConfig, MODULES, coerce/clamp/defaults (no disk I/O). Imports MODULES from _manifest and binds CONFIG_DEFAULTS via set_manifest_modules at import. Import schema names from here. |
plugin/framework/constants.py |
get_plugin_dir, AUTO_IMPORTS, worker pool ids |
plugin/framework/errors.py |
format_error_payload, ConfigError, safe_call |
plugin/framework/json_utils.py |
safe_json_loads (via client/errors.py) |
plugin/framework/i18n.py |
_() for translated errors |
plugin/framework/event_bus.py |
global_event_bus — imported by config.py |
plugin/framework/service.py |
ServiceBase — imported by event_bus.py |
plugin/framework/url_utils.py |
Endpoint normalization — imported by config.py |
plugin/framework/thread_guard.py |
background — imported by venv_worker.py |
plugin/framework/client/errors.py |
Bundled for other LibrePy callers (update check / Settings). Not used by the =PY() add-in. |
plugin/framework/client/__init__.py |
Package — lazy LLM/embeddings (PEP 562). LibrePy may import requests / provider_detection without loading llm_client. |
plugin/framework/__init__.py |
Package |
plugin/_manifest.py |
Generated (make manifest). MODULES is the source of truth for module.yaml defaults (config_schema binds derived tables; config_limits.py also reads MODULES) |
Not required for Layer 0 unless a higher layer pulls them in: llm_client.py, async_stream.py, tool.py, default_models.py, uno_context.py, worker_pool.py, appearance.py, …
| File | Role |
|---|---|
plugin/calc/python/addin_librepy.py |
LibrePy UNO add-in: python() (registers as org.extension.writeragent.PythonFunction) |
plugin/calc/python/addin_impl.py |
Shared PythonFunction class (WriterAgent addin.py and LibrePy both register it) |
plugin/calc/python/addin.py |
WriterAgent only — same add-in for the full OXT |
plugin/calc/python/function.py |
execute_python_addin, matrix session, spill, finalize_python_return |
plugin/calc/addin_common.py |
Shared add-in helpers |
plugin/calc/calc_addin_data.py |
Range → data, size limits, wire packing; _resolve_python_data for Run Python Script / trusted helpers |
plugin/calc/__init__.py |
Package (CalcError) |
plugin/calc/prompt_addin.py |
WriterAgent only — do not register in core |
plugin/calc/prompt_function.py |
WriterAgent only |
| File | Role |
|---|---|
plugin/scripting/venv_worker.py |
run_code_in_user_venv, PythonWorkerManager, warm worker, venv resolution |
plugin/scripting/ipc.py |
Pickle5 frame read/write |
plugin/scripting/payload_codec.py |
split_grid wire codec (host + child) |
plugin/scripting/sandbox.py |
VENV_AUTHORIZED_IMPORTS, scrub_subprocess_env, resolve_venv_python |
plugin/scripting/config_limits.py |
Timeout defaults/min/max, warm/long-trusted budgets |
plugin/scripting/session_manager.py |
Shared-kernel workbook sessions, reset |
plugin/scripting/module.yaml |
Config schema for Python settings |
plugin/scripting/__init__.py |
Package |
| File | Role |
|---|---|
plugin/scripting/venv/worker_harness.py |
Child stdin/stdout loop, trusted-action handler |
plugin/scripting/venv/venv_sandbox.py |
LocalPythonExecutor, user-code sandbox |
plugin/scripting/venv/coerce.py |
Grid → DataFrame coercion |
plugin/scripting/venv/__init__.py |
Package |
| File | Role |
|---|---|
plugin/contrib/smolagents/local_python_executor.py |
Restricted executor (send_tools takes a dict; no Tool import) |
plugin/contrib/smolagents/utils.py |
BASE_BUILTIN_MODULES, helpers |
plugin/contrib/smolagents/__init__.py |
Package |
WriterAgent-only (not in LibrePy): tools.py, agent_types.py, tool_validation.py, _function_type_hints_utils.py.
LibrePy bundle: scripts/build_librepy_oxt.py replaces smolagents/__init__.py with a slim stub (no agents import) so the venv worker can load local_python_executor without shipping chat-only smolagents modules.
| plugin/contrib/__init__.py | Package |
| File | Role |
|---|---|
plugin/__init__.py |
Package — must use pkgutil.extend_path when core and WriterAgent ship different plugin/ subtrees in separate OXTs (Prototype extension §2) |
Required for NumPy domain helpers, Vision OCR, and any run_trusted_worker_action path. Builds on Layer 0.
| File | Role |
|---|---|
plugin/scripting/client.py |
run_* host stubs, long-trusted timeout list |
plugin/scripting/trusted_rpc.py |
run_trusted_worker_action |
plugin/scripting/trusted_action_registry.py |
Domain → venv dispatcher wiring |
plugin/scripting/venv/trusted_dispatch.py |
Routes run_trusted_action to domain run_* |
plugin/scripting/venv/worker_heartbeat.py |
Heartbeat frames (long jobs) |
plugin/scripting/_lazy_venv.py |
Lazy plugin.scripting.* → plugin.scripting.venv.* |
plugin/scripting/helper_domain.py |
Shared helper-domain glue |
plugin/scripting/domain_registry.py |
RPS fast-path, picker origins, post-venv routing |
plugin/scripting/calc_functions_common.py |
Helper name frozensets (no numpy on host) |
plugin/scripting/import_policy.py |
LLM import-policy text generation |
plugin/scripting/venv_diagnostics.py |
Settings → Python Test self-check |
Core-build note: The whole trusted_action_registry.py and trusted_dispatch.py files ship. WriterAgent-only domains (sql, embeddings, languagetool, vale) are dead in LibrePy if invoked; that is accepted — do not add a core-only filter or second file. SQL picker already skips missing duckdb_sql.
Trusted RPC flow:
flowchart LR
host[Host facade e.g. viz.py]
client[client.py run_trusted_*]
rpc[trusted_rpc.py]
vw[venv_worker.py]
wh[worker_harness.py]
td[trusted_dispatch.py]
compute[venv/analysis.py etc]
host --> client --> rpc --> vw --> wh --> td --> compute
WriterAgent exposes Run Python Script… (extension/Addons.xcu → scripting.run_python_dialog), Reset Python Session (scripting.reset_python_session), and Text Analytics… (textanalytics.open_dialog). Wired from plugin/main.py or an equivalent core bootstrap job.
Entry: run_python_dialog(). Reuses run_code_in_user_venv. After success, writes into the active document (Writer or Calc). Draw/Impress shows an info message only.
flowchart LR
menu[Run Python Script menu]
dlg[Monaco or native dialog]
venv[run_code_in_user_venv]
w[Writer: format_result_for_writer]
insW[insert_content_at_position HTML]
c[Calc: insert_result_into_calc]
insC[write_formula_range from selection]
menu --> dlg --> venv
venv --> w --> insW
venv --> c --> insC
| App | Insertion | Result shaping |
|---|---|---|
| Writer | HTML at selection via insert_content_at_position |
format_result_for_writer — lists → tables, dicts → sections |
| Calc | Values from active selection via insert_result_into_calc |
Dict title/summary + tables; 1D/2D lists via write_formula_range |
| Draw/Impress | None (message box) | — |
Config keys: last_python_script_name_writer, last_python_script_name_calc, last_python_script_name_draw.
| File | Role |
|---|---|
plugin/scripting/python_runner.py |
Dialog, run, domain fast-paths, Writer/Calc branch |
plugin/scripting/python_runner_ui.py |
Native XDL fallback when Monaco unavailable |
plugin/scripting/document_scripts.py |
Document-attached scripts, init script storage |
plugin/chatbot/dialogs.py |
Shared XDL kit (add_dialog_*, msgbox) — lives under chatbot/ but is not the chat panel |
plugin/framework/uno_context.py |
get_ctx, get_desktop |
plugin/framework/worker_pool.py |
Via dialogs.py |
plugin/framework/appearance.py |
LO light/dark → Monaco theme |
plugin/doc/__init__.py |
Package marker. WriterAgent CommonModule (embeddings / document-research tool discovery) lives in unbundled common_module.py; LibrePy import of the package is inert. |
plugin/doc/doc_type.py |
is_writer / is_calc / is_draw / DocumentType (no analyzer) |
plugin/doc/udprops.py |
Document user-defined properties (session id, spill registry) |
plugin/doc/text_helpers.py |
Linebreaks, tracked-deletion reads, heading tree, document path |
plugin/doc/visual_helpers.py |
Graphic export for Vision egress |
plugin/writer/format.py |
HTML insert, mixed math segments. review_authors / content are ImportError-guarded (LibrePy applies HTML without split-author coloring or WriterAgent style lookup) |
plugin/writer/xhtml_style_postprocess.py |
HTML post-process |
plugin/calc/bridge.py |
Active sheet / document access |
plugin/calc/address_utils.py |
index_to_column for anchor cell |
plugin/calc/manipulator.py |
write_formula_range (1-D formula fill-down via formula_fill.py) |
plugin/calc/tabular_egress.py |
Tabular helper results → sheet |
plugin/calc/rich_html.py |
Rich HTML cell insert (Vision Calc egress) |
plugin/main_core.py |
LibrePy bootstrap — not plugin/main.py |
Refactor note: plugin/doc/doc_type.py holds is_writer / is_calc / DocumentType so Layer 0 (=PY()) and menu guards do not import document_helpers. Document properties use udprops.py. Writer text/path helpers used by RPS and text analytics live in text_helpers.py. document_helpers.py is WriterAgent-only (chat context / DocumentService) and must not import Calc at module load.
Optional gettext: filtered catalogs via make compile-translations-core (part of make build-core) — scripts/build_librepy_locales.py extracts strings from the LibrePy file closure only and bundles slim .mo files from build/generated/locales/, not the full WriterAgent locales/ tree.
| Artifact | Notes |
|---|---|
extension/idl/XPythonFunction.idl |
python(in string code, in any data) |
extension/idl/XPromptFunction.idl |
WriterAgent only — prompt() |
extension/XPythonFunction.rdb, XPromptFunction.rdb |
Built from IDL — scripts/rebuild_xprompt_rdb.sh |
extension/registry/.../CalcAddIns.xcu |
Core: python / PY node only; no prompt |
extension/META-INF/manifest.xml |
Filtered UNO entries + Python tree |
description.xml |
New extension identifier if not WriterAgent |
Service: com.sun.star.sheet.AddIn
Core menus (extension/Addons.xcu)
| Action | Core | WriterAgent only |
|---|---|---|
scripting.run_python_dialog |
Yes | |
scripting.edit_python_cell |
Yes (Layer 4) | |
scripting.reset_python_session |
Yes | |
writer.insert_latex_dialog |
Yes (Layer 6) | |
vision.open_settings |
Yes (Layer 5) | |
textanalytics.open_dialog |
Yes (Layer 3) | |
calc.convert_spreadsheet_to_python |
Yes | |
embeddings.search_dialog |
Yes | |
| Chat / review accelerators | Yes |
From scripting/module.yaml and vision/module.yaml:
- Settings → Python tab (
SettingsDialog.xdlpages) PythonTestProgressDialog.xdl(venv Test)PythonScriptDialog.xdl(native Run Python fallback)LatexInputDialog.xdl(native LaTeX fallback)VisionSettingsDialog.xdlMsgBoxWithCopyDialog.xdl,ErrorReportDialog.xdl(as used by dialogs)
Manifest reference: scripts/manifest_registry.py.
| Path | Layer | Notes |
|---|---|---|
vendor/latex2mathml/ |
6 | make vendor from requirements-vendor.txt; on sys.path at bootstrap |
vendor/json_repair/ |
0–2 | Config read + json_utils.py robust JSON parse |
User venv rocher |
4 | Monaco UI assets — not in OXT |
| User venv scientific stack | 0–5 | numpy, docling, pywebview, etc. — user-maintained |
LibrePy vendor subset: build_librepy_oxt.py copies only isodate, json_repair, and latex2mathml from vendor/ into plugin/lib/ (LIBREPY_VENDOR_PACKAGES). WriterAgent-only vendored packages are omitted from the core OXT:
| Package | WriterAgent use | LibrePy |
|---|---|---|
snowballstemmer |
Grammar stemming, web-research fluff words | Excluded |
websockets |
CDP browser tools (plugin/contrib/cdp/) |
Excluded |
defusedxml |
Embeddings locale XML (plugin/embeddings/) |
Excluded |
Full make vendor still installs all entries in requirements-vendor.txt for WriterAgent builds.
| Step | Detail |
|---|---|
| Manifest | make manifest → plugin/_manifest.py from module.yaml files. Core: at least scripting + vision; no embeddings |
| Bundle | Same OXT pipeline as WriterAgent, filtered to layer file lists; vendor copy uses LIBREPY_VENDOR_PACKAGES (isodate, json_repair, latex2mathml only) |
| Config path | Linux: ~/.config/libreoffice/{4,24}/user/writeragent.json (see config.py docstring) |
These share the venv worker or trusted RPC with core but must not ship in a core OXT (or require the full WriterAgent chat/LLM stack).
| Surface | Entry | Uses venv? | Inserts? |
|---|---|---|---|
Chat run_venv_python_script |
plugin/calc/python/venv.py |
Yes | No — JSON to agent |
Chat execute_python_script |
plugin/calc/python/executor.py |
No (embedded LO Python) | Optional target_range |
Additional files: plugin/calc/base.py, plugin/calc/inspector.py, plugin/framework/tool.py, full plugin/chatbot/* tool loop.
Tests: tests/calc/python/test_venv.py, tests/calc/python/test_executor.py
| Tool | Module | Core equivalent |
|---|---|---|
analyze_data |
plugin/calc/analysis.py |
Run Python Script → Analysis Helpers |
plot_data |
plugin/calc/viz.py |
Run Python Script → Viz Helpers |
forecast_data |
plugin/calc/forecast.py |
Run Python Script → Forecast |
optimize_data |
plugin/calc/optimize.py |
Run Python Script → Optimize |
symbolic_math |
plugin/calc/symbolic_math.py |
Run Python Script → Math Helpers |
extract_structure_from_image |
plugin/vision/vision_tools.py |
Run Python Script → Vision Helpers |
run_venv_python_script on Writer ignores data_range; the model inserts via Writer HTML tools, not python_runner. Menu-driven Writer insert: Layer 2.
| Area | Examples |
|---|---|
| LLM / chat | plugin/framework/client/llm_client.py, plugin/chatbot/*, =PROMPT() |
| Embeddings | plugin/embeddings/, WORKER_POOL_EMBEDDINGS |
| DuckDB | plugin/scripting/duckdb_sql.py, SQL picker origin |
| Spreadsheet import | calc.convert_spreadsheet_to_python |
| Grammar | LanguageTool / Vale / Harper under plugin/writer/locale/ |
| MCP / grammar UI | plugin/mcp/, plugin/writer/locale/ |
| Analysis Sub-Agent | ../calc/analysis-sub-agent.md |
| Sidebar audio mic | plugin/chatbot/audio_recorder.py — Settings may probe sounddevice; capture UI is chat |
flowchart TB
subgraph corePaths [Core extension]
PY["=PY()"]
menu[Run Python Script]
helpers[Trusted domain helpers]
end
subgraph writerAgent [WriterAgent only]
chat[Chat tool loop]
toolV[run_venv_python_script]
toolA[analyze_data etc]
end
PY --> helpers
menu --> helpers
chat --> toolV --> helpers
chat --> toolA --> helpers
Do not re-propose the Done items. They already landed; inventing a second split wastes review.
- Split the add-in —
addin_librepy.py/function.pyhave zerollm_clientimports.=PY()uses a local error formatter. - Narrow IDL — core RDB is
extension-core/idl/XPythonFunction.idlonly (no Prompt). - Separate extension id —
org.extension.librepyinextension-core/. Add-in implementation name staysorg.extension.writeragent.PythonFunctionso=PY()formulas stay portable. doc_type.py/udprops.py/text_helpers.py— type detection, document properties, and Writer text/path helpers.document_helpers.pyis WriterAgent-only and must not re-export those names.format.pylocal-imports —ops/review_authorsstay local; optionaledit_reviewviaImportError. Do not hoist.- Sandbox slimming —
local_python_executor.send_toolstakes a dict; LibrePy does not shiptools.py/agent_types.py/tool_validation.py/_function_type_hints_utils.py. pkgutil.extend_pathinplugin/__init__.py.- Slim bootstrap —
plugin/main_core.py;make build-core/deploy-core.deploy-coreremoves WriterAgent (xor install). plugin.framework.clientpackage init is lazy — HTTP / errors / provider detection only at import;LlmClient/ embeddings / analysis load on attribute access. Do not re-eager-importllm_clientin__init__.py.- LLM image gen stays out of LibrePy — ship
image_tools.py(graphic insert). Do not shipimage_utils.pyorimages.py. analyzer.pystays in the LibrePy bundle — reserved for later use; do not drop as dead weight.- Whole trusted registry ships — do not slim
trusted_action_registry.py,venv_diagnostics.py, or SQL picker leftovers for core. Missing modules already skip at runtime. - Shared config file — keep
writeragent.json. Do not inventpython_config.pyor rename the file unless option B needs a separate config. dialogs.pystays underplugin/chatbot/— it is a shared XDL kit, not the chat panel. Do not rename/move in a cleanup pass.
- WriterAgent strip of duplicate
=PY()/ Python menus / bundledplugin/scripting/(Prototype §7 steps 4–6). - WriterAgent
description.xmldependency onorg.extension.librepy. - Dual-install: one
=PY()add-in, chat tools import the core worker.
Allowlist source of truth: scripts/librepy_bundle_paths.py. tests/scripts/test_librepy_import_graph.py checks that top-level plugin.* imports of shipped modules resolve to allowlisted files, and that loading editor / function / sidebar / settings / python_runner does not import WriterAgent-only modules. Skip generated plugin._manifest, the build-replaced contrib/smolagents/__init__.py stub, and optional ImportError imports (Cython vec_pack.pack). Do not ship plugin/writer/__init__.py (it registers chat tools); rich_html.py imports plugin.writer.format directly.
| Option | Summary | Status |
|---|---|---|
| A — Core only (LibrePy.oxt) | Core OXT alone: =PY(), scientific menus, no chat. |
Supported today — make deploy-core |
| B — Core + WriterAgent | Core owns =PY() and the scripting stack; WriterAgent is AI-only overlay (=PROMPT(), chat, MCP). |
Not shipped — target architecture below |
| C — Duplicate (avoid) | Both extensions register =PY() / PYTHON or both ship full plugin/scripting/ → add-in conflict and/or import shadowing |
Avoid |
| D — WriterAgent only | Full WriterAgent OXT with its own Python stack (no LibrePy). | Supported today — make deploy; do not leave LibrePy installed at the same time |
Today: install LibrePy xor WriterAgent, not both. register-librepy-oxt removes WriterAgent first. register-built-oxt / make release removes LibrePy first — both OXTs register org.extension.writeragent.PythonFunction, and leaving LibrePy installed makes unopkg add fail with enabling: addin.py.
Target (not yet): B — core is standalone; WriterAgent assumes LibrePy is installed and does not register =PY() or duplicate scientific menus.
Prototype extension (standalone core; WriterAgent overlay later) {#prototype-extension-standalone-core--writeragent-overlay}
How this repo ships LibrePy.oxt so it works by itself (including =PY() / =PYTHON()), and the remaining steps for WriterAgent to become an optional AI layer that imports the core Python stack instead of duplicating it.
Shipped: standalone LibrePy (option A).
Not shipped: dual-install overlay (option B) — see Shipped so far.
flowchart TB
subgraph coreOXT [Core OXT standalone]
PY["=PY / =PYTHON"]
menus[Run Python Script Monaco Vision TeX]
worker[venv_worker trusted helpers]
cfg[writeragent.json scripting keys]
end
subgraph waOXT [WriterAgent OXT optional]
chat[Sidebar chat tools]
prompt["=PROMPT()"]
mcp[MCP grammar embeddings]
end
coreOXT --> PY
waOXT -->|import plugin.scripting.*| coreOXT
waOXT -->|no python add-in| coreOXT
| Install set | Expected | Status |
|---|---|---|
| Core only (LibrePy) | =PY() works; Run Python Script, Monaco, domains, Vision, LaTeX; Settings → Python; Python sidebar (Calc); no chat sidebar |
Shipped |
| Core + WriterAgent | Same single =PY() add-in; chat tools call plugin.scripting.venv_worker from core; =PROMPT() from WriterAgent only |
Not shipped |
| WriterAgent only | Full WriterAgent Python stack + AI + Python sidebar (superset of LibrePy surfaces) | Shipped (exclusive of LibrePy) |
| WriterAgent only after strip (no LibrePy, no bundled scripting) | Unsupported — needs LibrePy for =PY() |
Future under option B |
Do not reuse org.extension.writeragent for the core OXT — unopkg must be able to install both side by side. Example prototype id: org.extension.librepy (rename for upstream as needed).
| Artifact | WriterAgent today | Core prototype |
|---|---|---|
| Extension id | org.extension.writeragent |
org.extension.librepy |
description.xml |
extension/description.xml.tpl |
extension-core/description.xml (new identifier + display name) |
| UNO add-in impl | org.extension.writeragent.PythonFunction |
org.extension.writeragent.PythonFunction (alias — same namespace as WriterAgent so ORG.EXTENSION.WRITERAGENT.PYTHONFUNCTION.* formulas are portable; extension id stays org.extension.librepy) |
| IDL / RDB | org.extension.writeragent.PythonFunction.XPythonFunction |
Same IDL module path as WriterAgent (extension-core/idl/XPythonFunction.idl); rebuild via make rdb-core (needs unoidl-write from libreoffice-dev on Ubuntu / Debian, or libreoffice-fresh-sdk on Arch). scripts/rebuild_librepy_rdb.sh (via scripts/find_unoidl.sh, shared with scripts/rebuild_xprompt_rdb.sh) also searches PATH, $OO_SDK_HOME, macOS LibreOffice.app, and Windows Program Files; if the SDK is missing it reuses the committed extension-core/XPythonFunction.rdb (macOS/Windows CI). |
| CalcAddIns node | org.extension.writeragent.PythonFunction |
org.extension.writeragent.PythonFunction — keep function names py and python (users still type =PY()) |
| Protocol handler | org.extension.writeragent:* |
org.extension.librepy:* |
| Menu URLs | org.extension.writeragent:scripting.run_python_dialog |
org.extension.librepy:scripting.run_python_dialog |
| Startup job | org.extension.writeragent.Main |
org.extension.librepy.Main |
| Menubar node | org.extension.writeragent.menubar |
org.extension.librepy.menubar |
Update hardcoded strings in addin_librepy.py (implementationName, IDL import) — registers as org.extension.writeragent.PythonFunction while menus/protocol use org.extension.librepy. scripts/manifest_registry.py uses _PROTOCOL = "org.extension.writeragent" — core build needs a parallel constant or template substitution.
Core registers =PY() / =PYTHON() only. Do not register prompt_addin / =PROMPT() in core.
Both extensions use the top-level package name plugin. LibreOffice prepends each extension root to sys.path. Without a namespace package, whichever extension wins on sys.path owns all of plugin.* — the other extension’s subpackages may be invisible.
Required in both OXTs — update plugin/__init__.py:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)Then:
| OXT | Ships under plugin/ (examples) |
|---|---|
| Core | scripting/, calc/python/{addin,function,editor,...}, framework/ (config subset), writer/format, vision/, main_core.py, … — Layers 0–6 |
| WriterAgent | chatbot/, mcp/, calc/prompt_*, calc/base.py, calc/python/venv.py (chat tool), calc/analysis.py (chat tools), framework/prompts.py, embeddings/, main.py, framework/client/llm_*, … — WriterAgent-only |
Chat run_venv_python_script imports plugin.scripting.venv_worker — resolved from core’s extension root. WriterAgent must not bundle duplicate plugin/scripting/ in its OXT after the strip.
Remove from WriterAgent when core is the Python owner:
Manifest / UNO
plugin/calc/python/addin.pyextension/XPythonFunction.rdb(keepXPromptFunction.rdbfor=PROMPT())
Registry
- Entire
org.extension.writeragent.PythonFunctionnode inCalcAddIns.xcu— leave onlyPromptFunction/prompt.
Menus — remove from WriterAgent Addons.xcu (core owns these):
scripting.run_python_dialogscripting.edit_python_cellscripting.reset_python_sessionwriter.insert_latex_dialogvision.open_settingstextanalytics.open_dialog
Bootstrap — plugin/main.py: drop handlers for the above; keep chat, LLM settings, MCP, grammar, embeddings, review toolbar, etc.
OXT bundle — do not package core layer files (Summary inventory) in WriterAgent; only ship AI-specific trees.
Extension dependency — add to WriterAgent description.xml.tpl:
<dependencies>
<l:LibreOffice-minimal-version d:name="LibreOffice 24.8" value="24.8"/>
<OpenOffice.org-extension name="org.extension.librepy" optional="false"/>
</dependencies>Optional runtime guard: if core is missing, log a clear error and disable chat Python tools (not required if you always assume core is installed).
Shipped. plugin/main_core.py does not load chat, MCP, or grammar. It:
- Puts
vendor/onsys.path(forlatex2mathml) - Calls
init_loggingtheninit_config(ctx) - Adds downloaded Cython pack binaries to
sys.path(native_binaries.py) - Registers only core actions:
run_python_dialog,edit_python_cell,reset_python_session,insert_latex_dialog,vision.open_settings,textanalytics.open_dialog, Settings → Python - Installs the Calc cell context menu (CommandURL from
resolve_package_extension_id()so LibrePy right-click isorg.extension.librepy:scripting.edit_python_cell;GlobalEventBroadcasterso Calc windows opened afterOnStartAppstill get the interceptor) and Excel=PYauto-convert-on-open (best-effort) - Schedules the weekly update check
Registered as org.extension.librepy.Main in extension-core/Jobs.xcu and extension-core/META-INF/manifest.xml. Do not re-add a filtered main.py for core.
Recommended: keep writeragent.json for both extensions (CONFIG_FILENAME) so venv path, session mode, and timeouts are shared. Core owns Python Settings (plugin/librepy/settings.py: Python tab only; General/Image tabs hidden); WriterAgent Settings focus on LLM / AI keys.
Shared Settings helpers (both OXTs): plugin/scripting/venv_probe_ui.py (venv Test / download progress modal) and plugin/chatbot/settings_fields.py (module.yaml field-spec build/apply). LibrePy keeps Python-only chrome and Cython-only download local; list new shared files in scripts/librepy_bundle_paths.py.
WriterAgent-only module.yaml settings keys can carry librepy_exclude: true (e.g. scripting.ppt_master_data_path). LibrePy manifest generation (make manifest-core / generate_manifest.py --skip-writeragent-extension) omits those keys from _manifest_librepy.py and from generated SettingsDialog.xdl page 3 controls.
One venv + one shared-kernel session for =PY() and chat run_venv_python_script — desirable when both are installed.
Keep writeragent.json. Do not invent librepy.json + python_config.py unless option B later needs separate config files. LLM combobox helpers in config.py (endpoint_from_selector_text, model-placeholder checks) are ImportError-guarded; LibrePy falls back to normalize_endpoint_url and skips chat placeholder rejection. Dialog titles in shared Python UI use product_display_name (LibrePy vs WriterAgent).
Shipped layout (do not re-propose as new work):
extension-core/ # parallel to extension/
description.xml # org.extension.librepy
META-INF/manifest.xml # layers 0–6 UNO entries only
Jobs.xcu # org.extension.librepy.Main
ProtocolHandler.xcu # org.extension.librepy:*
Addons.xcu # core menus only
registry/CalcAddIns.xcu # PythonFunction only (no PROMPT)
idl/XPythonFunction.idl # same WriterAgent IDL module path (formula portability)
plugin/
main_core.py # slim bootstrap
__init__.py # pkgutil.extend_path (both OXTs)
Makefile
make build-core / deploy-core # LibrePy.oxt — **shipped** (removes WriterAgent on register)
make build / deploy # WriterAgent OXT (keep exclusive of LibrePy until overlay lands)
LibrePy menu Context: In extension-core/Addons.xcu, every submenu item must set an explicit Context property. Menubar icons for Run Python Script, Edit Python in Cell, and Settings come from AddonUI/Images (%origin%/assets/python_32.png / python_cell_32.png / gear_32.png); the sidebar hamburger sets the same files in Python. Do not rely on “empty Context = all applications” when the same submenu mixes Writer-only and Calc-only entries — LibreOffice may hide shared items (Settings, Run Python Script, Reset Python Session) in Calc. Shared items use the full menubar context string (Writer, Calc, Draw, Impress, Web, Global); doc-specific items set TextDocument or SpreadsheetDocument only. WriterAgent extension/Addons.xcu uses the same rule on shared items. Regression tests: tests/scripts/test_librepy_addons_xcu.py, tests/scripts/test_writeragent_addons_xcu.py.
Menu order: LibreOffice addon menus sort by oor:name, not XML document order — use sequential names (M01, M02, …) including separators (private:separator). Both products group working commands above an admin block (Settings, Vision OCR Settings, Reset Python Session) with Report bug… last. LibrePy’s command/admin cluster tracks WriterAgent’s shared actions (no AI/MCP/Debug/Search/Convert Sheet). WriterAgent keeps two separators (after Extend/Edit, before admin); LibrePy keeps one (before admin). Jupyter notebooks open via File → Open (no menu item).
make manifest-core: includescripting+visionmodule.yamlonly; excludeembeddings.make build-core: copy/filter files fromscripts/librepy_bundle_paths.py; vendor copy usesLIBREPY_VENDOR_PACKAGES(isodate,json_repair,latex2mathmlonly).make deploy-core:unopkgremove WriterAgent, then addorg.extension.librepy.
AddDonepkgutil.extend_pathtoplugin/__init__.py.AddDoneextension-core/skeleton +org.extension.librepyidentifiers +main_core.py.Done (LibrePy.oxt)build-core/deploy-core; verify core alone (=PY(), menus, Settings → Python Test).- Slim WriterAgent manifest (no
python_addin, no python menus, no duplicatescripting/in OXT). Not done - Add extension dependency in WriterAgent
description.xml. Not done - Verify core + WriterAgent: one
=PY()add-in, chat Python tools work via core worker. Not done — deploy still mutually excludes the other OXT
- Do not install both OXTs with both registering
py/pythonin CalcAddIns — Calc shows duplicate add-ins; behavior is undefined. LibrePy already registersorg.extension.writeragent.PythonFunction; WriterAgent must skip its own PythonFunction when LibrePy is present. - Do not ship two full copies of
plugin/scripting/withoutextend_path— import shadowing is nondeterministic. - Do not use
org.extension.writeragentas the core extension id — conflicts with existing WriterAgentunopkgidentity and protocol namespace.
Manual QA: close and reopen a Calc workbook that has an initialization script; the init script must run again on the first =PY() after reopen.
OXT surface: scripts/build_librepy_oxt.py copies only LIBREPY_DIALOG_FILES plus generated SettingsDialog.xdl / VisionSettingsDialog.xdl. It does not copy the whole extension/Dialogs/ or build/generated/Dialogs/ trees (those would pull WriterAgent chat/search/eval XDLs into LibrePy.oxt).
Manual QA fixture: tests/fixtures/numpy_domains_demo.ods — all domain helpers on one workbook (numpy_domains_demo.README.md).
| Component | License |
|---|---|
| WriterAgent (KeithCu / John Balis modifications) | GPL-3.0+ |
Vendored plugin/contrib/smolagents/ (Hugging Face) |
Apache-2.0 — retain notices in core OXT |
Vendored vendor/latex2mathml/ |
Per upstream license in vendor tree |
| In-process Calc sandbox lineage | Apache-2.0 note in executor.py (WriterAgent only; core uses venv path) |
Deduplicated union of Layers 0–6. Counts are approximate (~100 plugin/ paths); verify with import closure on a filtered bundle.
Allowlist: scripts/librepy_bundle_paths.py (LIBREPY_PLUGIN_FILES + dirs). Do not treat this summary as the build input.
Framework: config.py, config_schema.py, constants.py, errors.py, json_utils.py, i18n.py, event_bus.py, service.py, url_utils.py, thread_guard.py, client/errors.py, client/requests.py, client/ssl_helpers.py, client/provider_detection.py, lazy client/__init__.py, framework/__init__.py, _manifest.py (LibrePy: _manifest_librepy.py at generate time)
Calc: python/addin_librepy.py (not WriterAgent addin.py), python/function.py, addin_common.py, calc_addin_data.py, calc/__init__.py
Scripting host: venv_worker.py, ipc.py, payload_codec.py, sandbox.py, config_limits.py, session_manager.py, module.yaml, scripting/__init__.py
Doc (Layer 0): doc_type.py, udprops.py, text_helpers.py — type guards, document properties, and Writer text/path helpers without document_helpers
Venv child: venv/worker_harness.py, venv/venv_sandbox.py, venv/coerce.py, venv/__init__.py
Contrib smolagents: local_python_executor.py, utils.py, slim smolagents/__init__.py, contrib/__init__.py
Root: plugin/__init__.py, plugin/main_core.py, plugin/librepy/
client.py, trusted_rpc.py, trusted_action_registry.py, venv/trusted_dispatch.py, venv/worker_heartbeat.py, _lazy_venv.py, helper_domain.py, domain_registry.py, calc_functions_common.py, import_policy.py, venv_diagnostics.py
python_runner.py, python_runner_ui.py, document_scripts.py, chatbot/dialogs.py (shared XDL kit), uno_context.py, worker_pool.py, appearance.py, doc/visual_helpers.py, writer/format.py, writer/xhtml_style_postprocess.py, calc/bridge.py, calc/address_utils.py, calc/manipulator.py, calc/formula_fill.py, calc/tabular_egress.py, calc/rich_html.py, main_core.py, scripting/native_binaries.py (Cython pack download; audio_recorder_service.py is WriterAgent-only)
Host facades: analysis.py, viz.py, symbolic.py, units.py, forecast.py, optimize.py, quant.py, text_analytics.py, text_analytics_ui.py
Venv compute: venv/analysis.py, venv/viz.py, venv/symbolic.py, venv/units.py, venv/forecast.py, venv/optimize.py, venv/quant.py, venv/text_analytics.py
Calc runners/egress: calc/analysis_runner.py, calc/analysis_egress.py, calc/viz_auto_plot.py, calc/forecast_auto_plot.py, calc/quant_egress.py, calc/python/image_egress.py, calc/inspector.py
Writer images: image_tools.py + __init__.py (graphic insert). Do not ship image_utils.py or images.py (LLM image gen). analyzer.py stays in the bundle (later use).
editor_host.py, editor_ipc.py, calc/python/editor.py, calc/python/formula_edit.py, calc/python/editor_context_menu.py, calc/python/workbook_lifecycle.py (workbook OnUnload resets worker sessions), venv/editor_main.py, calc/excel_py_convert/ (Excel Python-in-Excel → DAG =PY auto-convert on open)
Dev reference only (not OXT): contrib/scripting/assets/editor/*
vision/__init__.py, vision/module.yaml, vision_common.py, vision_templates.py, vision_runner.py, vision_egress.py, vision_availability.py, vision/venv/vision.py, vision/venv/vision_docling.py, vision/venv/vision_paddle.py, vision/venv/vision_html_export.py, vision/venv/vision_layout_html.py, vision/venv/__init__.py, calc/vision_egress.py, chatbot/module_config_dialog.py
Omit vision_tools.py for menu-only core (chat extract_structure_from_image).
writer/math/latex_dialog.py, writer/math/math_mml_convert.py, writer/math/html_math_segment.py, writer/math/__init__.py
idl/XPythonFunction.idl, XPythonFunction.rdb, registry/.../CalcAddIns.xcu, META-INF/manifest.xml, Addons.xcu, WriterAgentDialogs/* (see Extension packaging), description.xml, vendor/latex2mathml/**
| Group | Packages |
|---|---|
| Scientific / EDA | numpy, pandas, scipy, scikit-learn, statsmodels, fg-data-profiling, pandas-montecarlo |
| Viz | matplotlib, seaborn |
| Symbolic | sympy |
| Units | pint |
| Text | spacy, textdescriptives, … |
| Quant | yfinance, pandas_ta, … |
| Vision | docling, rapidocr-paddle, pillow, css-inline |
| Monaco | pywebview, rocher, PyQt6, PyQt6-WebEngine, qtpy |
See numpy-domains.md and ../images/recognition.md for authoritative lists.
prompt_addin.py, prompt_function.py, plugin/framework/prompts.py, plugin/calc/base.py, plugin/embeddings/**, plugin/scripting/venv/duckdb_sql.py, plugin/calc/spreadsheet_import/**, plugin/scripting/calc_functions.py, plugin/scripting/venv/calc_functions*.py (deferred — see § Scope), plugin/calc/python/venv.py, plugin/calc/analysis.py (chat tool), plugin/vision/vision_tools.py (if menu-only), plugin/framework/client/llm_client.py, plugin/chatbot/panel.py, plugin/doc/document_helpers.py, plugin/writer/__init__.py (chat tool registration), plugin/writer/ops.py, plugin/writer/review_authors.py, plugin/writer/images/image_utils.py, plugin/writer/images/images.py, plugin/scripting/audio_recorder_service.py (LibrePy uses native_binaries.py for Cython pack download), plugin/contrib/smolagents/tools.py (and its import chain), grammar venv modules, full chat stack. Keep plugin/calc/analyzer.py in the core bundle (later use).
- Enabling NumPy & Python in LibreOffice — user guide, architecture,
=PY()behavior - Calc
=PY()data shapes —CalcRange, blanks/NaN, multi-range - NumPy domain helpers — Analysis, Viz, Symbolic, Units, Forecast, Optimize, Quant, Text
- Venv subprocess IPC & serialization — warm worker, protocol, wire formats
- Monaco editor dev plan — IPC, phases 2B–2F
- Image Recognition — Vision/OCR design
- Math / TeX import — LaTeX, MathML, StarMath pipeline
- Calc specialized toolsets — broader Calc chat/tools (WriterAgent scope)
Out of scope for core (separate PM/dev docs): ../embeddings.md, ../calc/duckdb-dev-plan.md, ../calc/spreadsheet-to-python-import.md
In core: Jupyter notebook import (File → Open .ipynb).
WriterAgent exposes Insert LaTeX Math… (writer.insert_latex_dialog) and embeds math in Run Python Script Writer HTML via format.py + html_math_segment.py.
Writer-only. Conversion runs in-process in LibreOffice’s embedded Python plus a hidden LibreOffice Math document load — no venv.
flowchart LR
menu[Insert LaTeX Math menu]
dlg[Monaco or XDL dialog]
l2m[latex2mathml in vendor/]
mml[convert_mathml_to_starmath]
ins[insert_writer_math_formula]
menu --> dlg --> l2m --> mml --> ins
| Step | Implementation |
|---|---|
| Dialog | latex_dialog.py — Monaco mode: latex or native XDL |
| Persist UI | last_latex_input, last_latex_display_block in config |
| LaTeX → MathML | Vendored latex2mathml on sys.path |
| MathML → StarMath | math_mml_convert.py — hidden Math doc |
| Insert | insert_writer_math_formula — TextEmbeddedObject with Math CLSID |
| File | Role |
|---|---|
plugin/writer/format.py |
_insert_mixed_html_and_math_at_cursor, insert_content_at_position |
plugin/writer/math/html_math_segment.py |
$…$, $$…$$, \(...\), \[...\], MathML segments |
plugin/writer/math/math_mml_convert.py |
LaTeX/MathML → StarMath (shared with menu) |
| Path | Role |
|---|---|
vendor/latex2mathml/ |
OXT-bundled via make vendor |
| LibreOffice Math | Required at runtime for conversion |
| Path | Why |
|---|---|
plugin/draw/math_insert.py |
Draw/Impress chat tool (WriterAgent) |
| Venv worker / smolagents | Menu path does not use subprocess |
Deeper design: ../writer/math-tex.md.
Shipped domains per numpy-domains.md: Analysis, Visualization, Symbolic Math, Units, Forecasting, Optimization, Quant, Text Analytics.
| Domain | Trusted RPC domain |
Menu / RPS | Chat tool (WriterAgent) |
|---|---|---|---|
| Analysis | analysis |
Run Python Script → Analysis Helpers | analyze_data |
| Viz | viz |
Run Python Script → Viz Helpers | plot_data |
| Symbolic | symbolic / math |
Run Python Script → Math Helpers | symbolic_math |
| Units | units |
Run Python Script → Units Helpers | — |
| Forecast | forecast |
Run Python Script → Forecast (header fast-path) | forecast_data |
| Optimize | optimize |
Run Python Script → Optimize (header fast-path) | optimize_data |
| Quant | quant |
Run Python Script → Quant Helpers | — |
| Text | text |
Tools → Text Analytics… | — |
| File | Role |
|---|---|
analysis.py |
Templates, run_trusted_analysis orchestration |
viz.py |
Viz templates, Writer/Calc egress |
symbolic.py |
SymPy helpers, math insert egress |
units.py |
Pint unit conversion |
forecast.py |
Time-series helpers |
optimize.py |
scipy.optimize helpers |
quant.py |
Quantitative finance helpers |
text_analytics.py |
spaCy / textdescriptives orchestration |
text_analytics_ui.py |
Text Analytics menu dialog |
| File | Role |
|---|---|
analysis.py |
run_analysis — full numpy/pandas/scipy stack |
viz.py |
run_viz — matplotlib/seaborn |
symbolic.py |
run_symbolic — SymPy |
units.py |
run_units — Pint |
forecast.py |
run_forecast — statsmodels |
optimize.py |
run_optimize |
quant.py |
run_quant |
text_analytics.py |
run_text_analytics |
Requires Layer 1 + domain_registry.py for RPS picker templates and fast-path headers (writeragent:forecast, etc.).
| File | Role |
|---|---|
calc/analysis_runner.py |
Range resolve + trusted analysis invoke |
calc/analysis_egress.py |
Analysis results → sheet |
calc/viz_auto_plot.py |
Auto-chart after viz |
calc/forecast_auto_plot.py |
Auto-chart after forecast |
calc/quant_egress.py |
Quant results → sheet |
calc/python/image_egress.py |
Matplotlib figure → sheet image |
calc/inspector.py |
read_range for data-bound helpers |
| File | Role |
|---|---|
writer/images/image_tools.py |
Insert plot images at cursor |
writer/images/image_utils.py |
WriterAgent only — ImageService / LlmClient image generation. Not in LibrePy. |
writer/math/math_mml_convert.py |
Symbolic math → Writer formula objects |
Whole-file caveat: writer/images/images.py and image_utils.py are LLM image generation — not in LibrePy. Core ships image_tools.py only.
| Path | Why |
|---|---|
plugin/calc/analysis.py, viz.py, forecast.py, optimize.py, symbolic_math.py |
Chat ToolBase wrappers — WriterAgent |
plugin/scripting/venv/duckdb_sql.py |
DuckDB — excluded |
plugin/embeddings/** |
Embeddings — excluded |
Monaco-based code editor (pywebview child in the user venv) for Calc formulas and ad-hoc scripts. Detail: monaco-editor-dev-plan.md.
| Feature | Entry |
|---|---|
| Edit Python in Cell… | calc/python/editor.py |
| Run Python Script… Monaco | python_runner.py + editor_host.py |
| Document scripts | document_scripts.py |
| Init script editor | init_script_editor.py + LibrePy sidebar button; Monaco load/save of document INIT script |
| Theme sync | appearance.py |
flowchart LR
host[editor_host.py in LO]
pipe[editor_ipc.py Pickle5]
child[venv/editor_main.py pywebview]
rocher[rocher Monaco assets in venv]
host --> pipe --> child --> rocher
| File | Role |
|---|---|
plugin/scripting/editor_host.py |
Spawn pywebview child, session, theme |
plugin/scripting/editor_ipc.py |
Pipe framing, errors |
plugin/calc/python/editor.py |
Edit Python in Cell… |
plugin/calc/python/xl_static_rewrite.py |
Optional save-time xl("A1") → data args (flag default off) |
plugin/calc/python/formula_edit.py |
Parse/rebuild =PY() formulas |
plugin/calc/python/editor_context_menu.py |
Cell context menu |
plugin/calc/python/cell_discovery.py |
Enumerate =PY() cells for sidebar |
plugin/calc/python/collabora_formula.py |
On open, rewrite Collabora GETPY OriginalNames to =PY() |
plugin/calc/python/diagnostics.py |
Bounded stdout/error log for sidebar |
plugin/calc/python/init_script_editor.py |
Monaco editor for workbook INIT script |
plugin/librepy/panel_factory.py |
LibrePy Calc sidebar UNO factory |
plugin/librepy/python_sidebar.py |
Sidebar controller (cells, diagnostics, actions; vertical stretch from XDL snapshot heights) |
plugin/librepy/sidebar_menus.py |
Header toolbar + hamburger (LibrePy-registered actions only; no Search/embeddings/MCP) |
plugin/calc/navigation.py |
Click-to-navigate from sidebar |
plugin/calc/excel_py_convert/ |
Excel Python-in-Excel → DAG =PY (auto on open + CLI) |
plugin/scripting/venv/editor_main.py |
Child process entry (runs in user venv) |
extension-core/registry/.../Sidebar.xcu |
LibrePyDeck + PythonPanel (Calc + Writer); deck icon assets/python_32.png (PSF two-snakes) |
extension-core/registry/.../Factories.xcu |
PythonPanelFactory registration |
extension/Dialogs/PythonSidebarDialog.xdl |
Sidebar layout |
Requires Layer 2 (appearance.py, document_scripts.py, python_runner.py) and Layer 0 worker for Run from Monaco.
LibrePy Python sidebar: Native deck (not chat) in Calc and Writer (always; not gated on NotebookBar). Header icons dispatch the same registered actions as the menus. LibrePy hamburger lists only handlers that exist in core (no Search/embeddings, MCP, or chat extend/edit). WriterAgent Python hamburger is the same popup as the chat sidebar (show_hamburger_menu). Calc: third header button is Edit Python in Cell; lists active-sheet =PY() cells and filtered diagnostics. Writer: third button is Insert LaTeX; Calc =PY() chrome is hidden; venv/session status plus Reset/Settings remain so tabbed UI still has the tools when the menubar is gone. Monaco remains a separate pywebview window. Visible content fields share leftover deck height; all controls scale horizontally except the 16px header icons. Frame-sized width hints are ignored so the deck does not flash skinny or keep a horizontal scrollbar.
| Asset | Location |
|---|---|
Monaco vs/, shell HTML/JS/CSS |
User venv rocher (via pip install rocher) |
| Dev reference copies | plugin/contrib/scripting/assets/editor/* |
uv pip install pywebview rocher PyQt6 PyQt6-WebEngine qtpyOptional: jedi (completion stub in editor_main.py).
Edit Python in Cell… falls back to a native LibreOffice dialog (same Save / Data / Save without =PY() chrome) when Monaco is unavailable. Running =PY() still uses the configured venv.
Local OCR and document layout via trusted Vision helpers. Manual path first per ../images/recognition.md. Core ships Run Python Script → Vision Helpers + Vision OCR Settings — not chat extract_structure_from_image.
flowchart LR
rps[Run Python Script Vision Helpers]
runner[vision_runner.py]
rpc[trusted RPC domain vision]
venv[vision/venv/vision.py]
egressW[writer/format.py HTML insert]
egressC[calc/vision_egress.py]
rps --> runner --> rpc --> venv
venv --> egressW
venv --> egressC
| File | Role |
|---|---|
vision_common.py |
Shared types/helpers |
vision_templates.py |
RPS script templates |
vision_runner.py |
Host orchestration, egress |
vision_egress.py |
Writer HTML insert routing |
vision_availability.py |
Stack gating |
module.yaml |
Settings schema → VisionSettingsDialog |
Omit vision_tools.py for menu-only core (LLM tool wrapper).
| File | Role |
|---|---|
vision.py |
run_vision dispatcher |
vision_docling.py |
Docling OCR/layout |
vision_paddle.py |
PaddleOCR fallback |
vision_html_export.py |
HTML prep for LO import |
vision_layout_html.py |
Structure HTML export |
Registered in trusted_action_registry.py as domain vision → trusted_dispatch.py.
| File | Role |
|---|---|
calc/vision_egress.py |
Calc HTML / structured grid insert |
calc/rich_html.py |
insert_cell_html_rich |
writer/format.py |
Writer HTML at cursor; run_writer_mutation_with_optional_review wraps insert in EditReviewSession when WriterAgent’s edit_review module is co-installed (LibrePy alone applies directly) |
doc/visual_helpers.py |
Export graphic to bytes |
| Resource | Role |
|---|---|
Menu vision.open_settings |
chatbot/module_config_dialog.py |
VisionSettingsDialog.xdl |
Generated from vision/module.yaml |
| Settings → Python Test | Vision Libraries probe in venv_diagnostics.py. WriterAgent-only groups (Embeddings, Audio) may still appear; do not slim this file for LibrePy. |
uv pip install docling rapidocr-paddle numpy pillow css-inline
# optional fallback: paddleocr paddlepaddleModels are not bundled in the OXT; user venv installs them.
| Path | Role |
|---|---|
vision_tools.py |
extract_structure_from_image LLM tool |
plugin/framework/tool.py |
Tool registration |
| Draw/Impress page-positioned OCR | Deferred — ../images/recognition.md Phase 1b.2 |