@rho-workspace/rho 0.5.6 → 0.5.7

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 (211) hide show
  1. package/dist/agent-connect.d.ts +36 -2
  2. package/dist/agent-connect.js +64 -11
  3. package/dist/answer-board.js +3 -3
  4. package/dist/bridge.js +6 -1
  5. package/dist/browser-access.d.ts +1 -0
  6. package/dist/browser-access.js +5 -3
  7. package/dist/capabilities.d.ts +21 -5
  8. package/dist/capabilities.js +6 -4
  9. package/dist/cli.js +76 -11
  10. package/dist/composition.d.ts +1 -1
  11. package/dist/composition.js +13 -3
  12. package/dist/connect-cli.d.ts +50 -0
  13. package/dist/connect-cli.js +128 -0
  14. package/dist/contract.generated.d.ts +1191 -278
  15. package/dist/contract.generated.js +1 -1
  16. package/dist/contracts/owners.generated.js +845 -47
  17. package/dist/conversation-presentation.d.ts +9 -2
  18. package/dist/conversation-presentation.js +3 -1
  19. package/dist/conversations.d.ts +154 -8
  20. package/dist/conversations.js +147 -24
  21. package/dist/discovery.js +2 -2
  22. package/dist/extension-contract.d.ts +19 -0
  23. package/dist/extension-contract.js +15 -6
  24. package/dist/extension-markets.d.ts +74 -0
  25. package/dist/extension-markets.js +166 -0
  26. package/dist/extensions.d.ts +29 -1
  27. package/dist/extensions.js +13 -4
  28. package/dist/external-work.d.ts +22 -0
  29. package/dist/external-work.js +91 -0
  30. package/dist/plugin-cli.js +75 -3
  31. package/dist/plugin-inventory.d.ts +56 -0
  32. package/dist/plugin-inventory.js +41 -0
  33. package/dist/plugin-inventory.json +1581 -102
  34. package/dist/plugin-presentation.d.ts +105 -2
  35. package/dist/plugin-presentation.js +59 -5
  36. package/dist/plugins.d.ts +14 -3
  37. package/dist/plugins.js +12 -6
  38. package/dist/project-notes.js +1 -1
  39. package/dist/protocol.d.ts +6 -0
  40. package/dist/protocol.js +30 -12
  41. package/dist/replay.js +4 -0
  42. package/dist/response-contracts.js +26 -11
  43. package/dist/running.d.ts +10 -0
  44. package/dist/running.js +45 -0
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +71 -8
  47. package/dist/state.d.ts +8 -0
  48. package/dist/state.js +8 -0
  49. package/dist/studio-layout.js +3 -3
  50. package/dist/task-frame.d.ts +1 -1
  51. package/dist/timeline-events.d.ts +1 -0
  52. package/dist/timeline-events.js +8 -1
  53. package/dist/ui-context.d.ts +73 -0
  54. package/dist/ui-context.js +98 -0
  55. package/dist/ui-contract.d.ts +23 -0
  56. package/dist/web/.vite/manifest.json +133 -74
  57. package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
  58. package/dist/web/assets/choices-D8vGwsl2.js +1 -0
  59. package/dist/web/assets/{console-Trqc0swD.js → console-Dyuo1q4N.js} +4 -4
  60. package/dist/web/assets/{dist-pF902jHA.js → dist-CSiCyBml.js} +1 -1
  61. package/dist/web/assets/{dist-DpIHRk3B.js → dist-D9FLb9Hm.js} +1 -1
  62. package/dist/web/assets/{dist-eMx6b8N1.js → dist-FXbTBwML.js} +1 -1
  63. package/dist/web/assets/{dist-DZmszrjG.js → dist-l3DUhbw5.js} +1 -1
  64. package/dist/web/assets/{explore-in-BCHqjJ7B.js → explore-in-BfSFGRh2.js} +1 -1
  65. package/dist/web/assets/graph-BU2_o5uA.js +2 -0
  66. package/dist/web/assets/index-C16mXVvk.css +1 -0
  67. package/dist/web/assets/index-CWg9XYXx.js +952 -0
  68. package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
  69. package/dist/web/assets/{notes-meaning-DcVGUBBY.js → notes-meaning-T2m5zQfv.js} +1 -1
  70. package/dist/web/assets/overview-PzfLJCo-.js +6 -0
  71. package/dist/web/assets/preview-BguEdsza.js +1 -0
  72. package/dist/web/assets/previews-pWNik-2g.js +3 -0
  73. package/dist/web/assets/{review-DYP0aCXb.js → review-Dy_85lcH.js} +1 -1
  74. package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
  75. package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
  76. package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
  77. package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
  78. package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
  79. package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
  80. package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
  81. package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
  82. package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
  83. package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
  84. package/dist/web/assets/{skills-1ec0Z-jx.js → skills-Dyz7mBlc.js} +1 -1
  85. package/dist/web/assets/{skills-work-DNexV4OJ.js → skills-work-BEGqlCkx.js} +1 -1
  86. package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
  87. package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
  88. package/dist/web/assets/work-turns-DSabCogR.js +1 -0
  89. package/dist/web/assets/{workspace-COmCbGV3.js → workspace-DfjBL--r.js} +2 -2
  90. package/dist/web/composition.json +138 -83
  91. package/dist/web/index.html +3 -3
  92. package/dist/web/renderer.json +1 -1
  93. package/dist/web-assets.js +2 -2
  94. package/dist/work-graph.d.ts +11 -1
  95. package/dist/work-graph.js +16 -5
  96. package/dist/workspace.d.ts +2 -2
  97. package/dist/workspace.js +8 -7
  98. package/docs/FRONTEND.md +53 -11
  99. package/docs/HEADLESS.md +104 -11
  100. package/docs/THIRD-PARTY-PLUGINS.md +75 -3
  101. package/node_modules/@rho-workspace/plugin-agent/README.md +5 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +31 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +45 -10
  104. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +19 -3
  105. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +3 -2
  106. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +1 -1
  107. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
  108. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
  109. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
  110. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
  111. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
  112. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +1 -1
  113. package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
  114. package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
  115. package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
  116. package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
  117. package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
  118. package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
  119. package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
  120. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
  121. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
  122. package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
  123. package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
  124. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
  125. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
  126. package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
  127. package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
  128. package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
  129. package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
  130. package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
  131. package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
  132. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
  133. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
  136. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
  137. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
  138. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
  139. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
  140. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
  141. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
  142. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
  143. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  144. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
  145. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
  146. package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
  147. package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
  148. package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
  149. package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
  150. package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
  151. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
  152. package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  153. package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
  154. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
  155. package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
  156. package/node_modules/jose/package.json +1 -1
  157. package/package.json +6 -4
  158. package/protocol/index.json +91 -55
  159. package/protocol/operations/agent.sessions.list.json +13 -5
  160. package/protocol/operations/agent.sessions.read.json +13 -5
  161. package/protocol/operations/capability.list.json +382 -6
  162. package/protocol/operations/composition.apply.json +10 -2
  163. package/protocol/operations/composition.delete.json +845 -0
  164. package/protocol/operations/composition.read.json +10 -2
  165. package/protocol/operations/composition.undo.json +10 -2
  166. package/protocol/operations/conversation.agents.add.json +346 -0
  167. package/protocol/operations/conversation.agents.remove.json +340 -0
  168. package/protocol/operations/conversation.decide.json +196 -0
  169. package/protocol/operations/conversation.permission.json +196 -0
  170. package/protocol/operations/conversation.read.json +196 -0
  171. package/protocol/operations/conversation.send.json +265 -0
  172. package/protocol/operations/conversation.settings.check.json +213 -495
  173. package/protocol/operations/conversation.settings.read.json +211 -490
  174. package/protocol/operations/conversation.settings.update.json +235 -554
  175. package/protocol/operations/conversation.start.json +267 -5
  176. package/protocol/operations/conversation.stop.json +196 -0
  177. package/protocol/operations/extension.describe.json +39 -0
  178. package/protocol/operations/extension.install.json +62 -0
  179. package/protocol/operations/extension.list.json +164 -1
  180. package/protocol/operations/extension.market.change.json +210 -0
  181. package/protocol/operations/figure.read.json +265 -0
  182. package/protocol/operations/figure.select.json +157 -0
  183. package/protocol/operations/kernel.interrupt.json +119 -0
  184. package/protocol/operations/kernel.read.json +119 -0
  185. package/protocol/operations/kernel.run.json +119 -0
  186. package/protocol/operations/observation.snapshot.json +416 -8
  187. package/protocol/operations/project.openFile.json +10 -2
  188. package/protocol/operations/session.grant.json +4 -0
  189. package/protocol/operations/shared.json +1 -0
  190. package/protocol/operations/view.close.json +10 -2
  191. package/protocol/operations/view.protection.set.json +10 -2
  192. package/vendor/composition.json +177 -112
  193. package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
  194. package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
  195. package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
  196. package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
  197. package/web/theme.css +5 -0
  198. package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
  199. package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
  200. package/dist/web/assets/choices-D-YfnFP6.js +0 -1
  201. package/dist/web/assets/graph-lENgfQNH.js +0 -2
  202. package/dist/web/assets/index-B3_OVy91.css +0 -1
  203. package/dist/web/assets/index-BpL_Kl_s.js +0 -892
  204. package/dist/web/assets/overview-CDnVKlOt.js +0 -6
  205. package/dist/web/assets/preview-BEC1E9MK.js +0 -1
  206. package/dist/web/assets/studio-process-EYQgikxq.js +0 -2
  207. package/dist/web/assets/timeline-events-Chj2Gbx7.js +0 -1
  208. package/dist/web/assets/work-turns-CT9RVcWY.js +0 -1
  209. package/vendor/rho-workspace-plugin-agent-0.4.30.tgz +0 -0
  210. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  211. package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
@@ -1,6 +1,6 @@
1
1
  # Notebook
2
2
 
3
- `@rho-workspace/plugin-kernel` 0.5.4 keeps a live R session and a live Python
3
+ `@rho-workspace/plugin-kernel` 0.5.7 keeps a live R session and a live Python
4
4
  session for a project, which an Agent and the person share, on this machine or on a
5
5
  server: one the person manages with serverctl, reached through it, or one reached with
6
6
  ssh.
@@ -17,7 +17,27 @@ device that stays open for the session, as the console's does: a later cell can
17
17
  over an earlier cell's plot (a line over its points) and reports the plot it changed,
18
18
  and each page is recorded before a new one starts, so a sourced script that draws two
19
19
  plots keeps both. Calls that only set graphics state (`par`, `palette`, `layout`) do not
20
- make a page new. Python closes its figures after each cell. After each cell the
20
+ make a page new. A host constructed with `extensions.r` and `extensions.python` hands those
21
+ files to each local R or Python session it starts (`RHO_KERNEL_EXTENSIONS`); the Figures
22
+ plugin's describers are such files,
23
+ and a plot it described names its `figure` (layers, marks, how many are bound) or why it
24
+ was not described (`figureProblem`), and `figureRecord` returns the record beside the
25
+ PNG. A page the code saves to a project file on a device of its own (`ggsave`) is found by
26
+ the extension's `saved_page()` and described when the cell ends, at the size it was saved
27
+ at; the cell's `saved` names each such file with its bytes and SHA-256 and the figure's
28
+ summary or `figureProblem`, and `savedFigure(sha256)` returns the record of the newest cell
29
+ that saved those bytes. A file written again since has other bytes and no record. A device
30
+ the code opens on a file (`png()`, `jpeg()`, `tiff()`, `bmp()`, `svg()`, `pdf()`, `cairo_pdf()`)
31
+ is noted with its file: the runner traces those functions and `dev.off()` in grDevices, so
32
+ `png` prints as a traced function. Its pages are recorded as they are drawn, and when
33
+ `dev.off()` closes it the last page (each page, for a name numbered with `%d`) is offered to
34
+ the extension like a ggsave page; a PDF of several pages is named with a `figureProblem`. A
35
+ device the code left open stays the current device in the next cell. A device opened through a
36
+ copy a package imported before the session started is not seen.
37
+ In Python, `Figure.savefig` (and so `plt.savefig`) is watched once `matplotlib.figure` is
38
+ imported: a figure saved to a file in the project is described as savefig wrote it and named
39
+ in `saved` when the cell ends, also when the cell closed the figure. Python closes its
40
+ figures after each cell. After each cell the
21
41
  session lists its variables with their type, size and a one-line summary.
22
42
  Each cell also names the values it made or changed (`defined`) and, when neither list
23
43
  was cut, those it removed (`removed`). It names the values it read (`used`): the names
@@ -187,7 +207,11 @@ The page supplies the scoped client and the project's facts (`UiSnapshot`: the p
187
207
  the page's cursor and the conversations, so a cell names the Agent that ran it), and
188
208
  callbacks to open a value, a server's file or a conversation as it places them. Inside a
189
209
  conversation's record it passes a `SessionScope`: the cells that conversation's Agent and
190
- the person ran since it began, up to the moment shown. `KernelShell` takes the page's code
210
+ the person ran since it began, up to the moment shown. Native clients sharing a connection
211
+ can supply `cellIds` from their retained tool receipts; these select exact Agent cells
212
+ instead of treating the connection principal as one conversation. An empty list claims
213
+ no Agent cells. This selection does not reconstruct earlier output states.
214
+ `KernelShell` takes the page's code
191
215
  highlighting and Markdown; without them code stays plain and a server's skill shows as
192
216
  written. The views add their own styles once, in the page's color and type variables, and
193
217
  use its base controls (`text-button`, `primary`, `secondary`, `inline-error`).
@@ -105,12 +105,24 @@ export declare const sessionSchema: {
105
105
  properties: Record<string, unknown>;
106
106
  required: string[];
107
107
  };
108
+ export declare const figureSummarySchema: {
109
+ type: string;
110
+ additionalProperties: boolean;
111
+ properties: Record<string, unknown>;
112
+ required: string[];
113
+ };
108
114
  export declare const outputSchema: {
109
115
  type: string;
110
116
  additionalProperties: boolean;
111
117
  properties: Record<string, unknown>;
112
118
  required: string[];
113
119
  };
120
+ export declare const savedFigureSchema: {
121
+ type: string;
122
+ additionalProperties: boolean;
123
+ properties: Record<string, unknown>;
124
+ required: string[];
125
+ };
114
126
  export declare const cellSchema: {
115
127
  type: string;
116
128
  additionalProperties: boolean;
@@ -72,7 +72,12 @@ export const sessionSchema = obj({
72
72
  // (.venv/bin/python). Sessions recorded before have none.
73
73
  program: maybe({ type: 'string' }),
74
74
  }, ['sessionId', 'language', 'state', 'version', 'pid', 'startedAt', 'startedBy', 'endedAt', 'ending', 'stop', 'problem', 'variables', 'variablesOmitted', 'variablesAt', 'cells']);
75
- export const outputSchema = obj({ kind: { enum: ['stdout', 'stderr', 'warning', 'error', 'plot'] }, text: { type: 'string' }, plot: { type: 'integer', minimum: 1 }, width: { type: 'integer' }, height: { type: 'integer' } }, ['kind']);
75
+ // A described plot names what it drew: its describer, layers, marks and how many are bound to rows.
76
+ export const figureSummarySchema = obj({ describer: text, kind: { type: 'string' }, title: { type: 'string' }, layers: { type: 'integer', minimum: 0 }, marks: { type: 'integer', minimum: 0 }, bound: { type: 'integer', minimum: 0 }, geoms: { type: 'array', items: { type: 'string' } } }, ['describer', 'layers', 'marks', 'bound', 'geoms']);
77
+ export const outputSchema = obj({ kind: { enum: ['stdout', 'stderr', 'warning', 'error', 'plot'] }, text: { type: 'string' }, plot: { type: 'integer', minimum: 1 }, width: { type: 'integer' }, height: { type: 'integer' }, figure: figureSummarySchema, figureProblem: { type: 'string' } }, ['kind']);
78
+ // A page the cell saved to a project file (ggsave) that an extension described: the file as the
79
+ // cell left it, and the figure's summary or why it has none.
80
+ export const savedFigureSchema = obj({ number: { type: 'integer', minimum: 1 }, path: text, bytes: { type: 'integer', minimum: 0 }, sha256: { type: 'string', pattern: '^[a-f0-9]{64}$' }, figure: figureSummarySchema, figureProblem: { type: 'string' } }, ['number', 'path', 'bytes', 'sha256']);
76
81
  export const cellSchema = obj({
77
82
  cellId, changeId, by: text, sessionId, language, code: { type: 'string' }, purpose: maybe({ type: 'string' }),
78
83
  queuedAt: time, startedAt: maybe(time), endedAt: maybe(time), state: { enum: [...cellStates] },
@@ -92,6 +97,8 @@ export const cellSchema = obj({
92
97
  used: { type: 'array', items: text }, files: { type: 'array', items: obj({ path: text, how: { enum: ['read', 'wrote'] } }) },
93
98
  // The program and version that ran it. Cells recorded before have neither.
94
99
  program: maybe({ type: 'string' }), version: maybe({ type: 'string' }),
100
+ // Plots the code saved to project files, each kept as a figure of the file's bytes.
101
+ saved: { type: 'array', items: savedFigureSchema },
95
102
  }, ['cellId', 'changeId', 'by', 'sessionId', 'language', 'code', 'purpose', 'queuedAt', 'startedAt', 'endedAt', 'state', 'number', 'outputs', 'bytes', 'kept', 'cut', 'interrupt', 'problem']);
96
103
  // One value of a live session as its session describes it: the kind of thing it is, short facts,
97
104
  // and one page of its rows, its elements or its parts, each part with the code that reaches it.
@@ -46,6 +46,16 @@ export interface Asked {
46
46
  by: string;
47
47
  at: string;
48
48
  }
49
+ export interface FigureSummary {
50
+ describer: string;
51
+ kind?: string;
52
+ title?: string;
53
+ layers: number;
54
+ marks: number;
55
+ bound: number;
56
+ geoms: string[];
57
+ notes?: string[];
58
+ }
49
59
  export type Output = {
50
60
  kind: 'stdout' | 'stderr' | 'warning' | 'error';
51
61
  text: string;
@@ -54,7 +64,17 @@ export type Output = {
54
64
  plot: number;
55
65
  width: number;
56
66
  height: number;
67
+ figure?: FigureSummary;
68
+ figureProblem?: string;
57
69
  };
70
+ export interface SavedFigure {
71
+ number: number;
72
+ path: string;
73
+ bytes: number;
74
+ sha256: string;
75
+ figure?: FigureSummary;
76
+ figureProblem?: string;
77
+ }
58
78
  export interface CellRecord {
59
79
  cellId: string;
60
80
  changeId: string;
@@ -83,6 +103,7 @@ export interface CellRecord {
83
103
  }>;
84
104
  program?: string | null;
85
105
  version?: string | null;
106
+ saved?: SavedFigure[];
86
107
  }
87
108
  export type InspectInput = {
88
109
  language: Language;
@@ -105,6 +126,7 @@ export declare const kernelLimits: {
105
126
  maxOutputBytes: number;
106
127
  maxPlots: number;
107
128
  maxPlotBytes: number;
129
+ maxFigureBytes: number;
108
130
  maxVariables: number;
109
131
  maxWaitMs: number;
110
132
  startTimeoutMs: number;
@@ -157,6 +179,10 @@ export declare class KernelHost {
157
179
  ssh?: string;
158
180
  serverctl?: string;
159
181
  onChange?: () => void;
182
+ extensions?: {
183
+ r?: string[];
184
+ python?: string[];
185
+ };
160
186
  });
161
187
  private program;
162
188
  private named;
@@ -268,6 +294,41 @@ export declare class KernelHost {
268
294
  width: number;
269
295
  height: number;
270
296
  }>;
297
+ figureRecord(cellId: string, plot: number): Promise<{
298
+ cell: {
299
+ cellId: string;
300
+ number: number;
301
+ by: string;
302
+ purpose: string | null;
303
+ code: string;
304
+ language: "r" | "python";
305
+ endedAt: string | null;
306
+ sessionId: string;
307
+ };
308
+ output: {
309
+ kind: "plot";
310
+ plot: number;
311
+ width: number;
312
+ height: number;
313
+ figure?: FigureSummary;
314
+ figureProblem?: string;
315
+ };
316
+ bytes: NonSharedBuffer;
317
+ }>;
318
+ savedFigure(sha256: string): Promise<{
319
+ cell: {
320
+ cellId: string;
321
+ number: number;
322
+ by: string;
323
+ purpose: string | null;
324
+ code: string;
325
+ language: "r" | "python";
326
+ endedAt: string | null;
327
+ sessionId: string;
328
+ };
329
+ output: SavedFigure;
330
+ bytes: NonSharedBuffer;
331
+ }>;
271
332
  close(): Promise<void>;
272
333
  }
273
334
  export declare const plugin: {
@@ -4,7 +4,8 @@ import { constants as fsConstants } from 'node:fs';
4
4
  import net from 'node:net';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { randomBytes } from 'node:crypto';
7
+ import { createHash, randomBytes } from 'node:crypto';
8
+ import { createReadStream } from 'node:fs';
8
9
  import { atomicBytes, atomicJson, confined, digest, now, readJson, requireThat, stateDirectory, timelineLabel } from '@rho-workspace/plugin-contracts';
9
10
  import { ownGroup, signalGroup } from './process-group.js';
10
11
  import { checkCommand, copyCommand, listCommand, readCheck, readCopy, readList, sessionCommand, signalCommand, Ssh } from './places.js';
@@ -14,9 +15,10 @@ import { operationPresentation, presentation, viewPresentations } from './presen
14
15
  // The protected resources are this machine's processes and memory, and the project's state
15
16
  // folder. At most one session runs per language, and at most maxQueued cells wait in each. A
16
17
  // cell keeps its first maxOutputBytes of output (the rest is counted) and at most maxPlots
17
- // plots of at most maxPlotBytes each; at most maxCells cells are kept, the oldest going first
18
- // with their plots. The session lists at most maxVariables variables. A run waits at most
19
- // maxWaitMs for its cell. What a session's own code allocates is not bounded here.
18
+ // plots of at most maxPlotBytes each, each with a figure record of at most maxFigureBytes; at
19
+ // most maxCells cells are kept, the oldest going first with their plots. The session lists at
20
+ // most maxVariables variables. A run waits at most maxWaitMs for its cell. What a session's own
21
+ // code allocates is not bounded here.
20
22
  // Reading a value shows at most inspectColumns columns, a page of at most 200 rows, elements or
21
23
  // parts (inspectRows unless asked), cells of inspectCellChars characters and a printed form of
22
24
  // inspectTextBytes; a table sorts only up to inspectSortRows rows. A reading that takes longer
@@ -32,11 +34,37 @@ export const lineageLimits = { files: 32 };
32
34
  // after serverctlReadMs; a session through serverctl lasts at most serverctlSessionS, serverctl's
33
35
  // own limit for a command, after which serverctl ends it; a server's skill is read up to
34
36
  // serverSkillBytes.
35
- export const kernelLimits = { maxCells: 500, maxQueued: 16, maxCodeBytes: 65536, maxOutputBytes: 256 * 1024, maxPlots: 8, maxPlotBytes: 4 * 1024 * 1024, maxVariables: 200, maxWaitMs: 600000, startTimeoutMs: 30000, stopGraceMs: 3000, maxEndedSessions: 20,
37
+ export const kernelLimits = { maxCells: 500, maxQueued: 16, maxCodeBytes: 65536, maxOutputBytes: 256 * 1024, maxPlots: 8, maxPlotBytes: 4 * 1024 * 1024, maxFigureBytes: 16 * 1024 * 1024, maxVariables: 200, maxWaitMs: 600000, startTimeoutMs: 30000, stopGraceMs: 3000, maxEndedSessions: 20,
36
38
  inspectRows: 50, inspectColumns: 50, inspectChildren: 200, inspectCellChars: 120, inspectTextBytes: 16 * 1024, inspectSortRows: 1000000, inspectTimeoutMs: 10000, inspectGiveUpMs: 5000,
37
39
  maxPlaces: 16, remoteStartTimeoutMs: 90000, remoteCheckTimeoutMs: 60000, remoteListTimeoutMs: 30000, remoteSignalTimeoutMs: 15000, remoteEntries: 500, remoteListBytes: 1024 * 1024, remoteCopyBytes: 64 * 1024 * 1024, remoteCopyTimeoutMs: 600000, maxCopies: 500,
38
40
  serverctlReadMs: 15000, serverctlSessionS: 7 * 24 * 3600, serverSkillBytes: 256 * 1024 };
39
41
  const markStart = '\x1eRHO:';
42
+ // The SHA-256 of a file's bytes, read in chunks.
43
+ async function fileDigest(file) {
44
+ const hash = createHash('sha256');
45
+ let bytes = 0;
46
+ for await (const chunk of createReadStream(file)) {
47
+ hash.update(chunk);
48
+ bytes += chunk.length;
49
+ }
50
+ return { sha256: hash.digest('hex'), bytes };
51
+ }
52
+ // What an extension said of a plot, cut to the summary a cell keeps.
53
+ function figureSummary(value) {
54
+ const n = (x) => Number.isSafeInteger(x) && x >= 0 ? x : 0;
55
+ const t = (x) => typeof x === 'string' ? x.slice(0, 200) : undefined;
56
+ const geoms = Array.isArray(value.geoms) ? value.geoms : typeof value.geoms === 'string' ? [value.geoms] : [];
57
+ return { describer: t(value.describer) ?? 'unknown', ...(t(value.kind) ? { kind: t(value.kind) } : {}), ...(t(value.title) ? { title: t(value.title) } : {}),
58
+ layers: n(value.layers), marks: n(value.marks), bound: n(value.bound), geoms: geoms.filter((g) => typeof g === 'string').slice(0, 32).map((g) => g.slice(0, 64)),
59
+ // What the describer noticed about the figure (marks drawn elsewhere than their values, labels
60
+ // naming other rows than the values they sit beside), so the Agent reads it with the cell.
61
+ ...(Array.isArray(value.notes) && value.notes.length ? { notes: value.notes.filter((t) => typeof t === 'string').slice(0, 4).map((t) => t.slice(0, 300)) } : {}) };
62
+ }
63
+ // R on Windows sets these categories from the environment, and Windows has no C.UTF-8: given one,
64
+ // as an Agent's Git Bash gives LC_CTYPE, R warns at startup, runs in the C locale and prints
65
+ // "3 × 2" as "3 <U+00D7> 2". Without them a session takes the system's own locale, UTF-8 there.
66
+ const posixUtf8 = /^(C|POSIX)\.UTF-?8$/i, rLocale = /^LC_(ALL|CTYPE|COLLATE|TIME|MONETARY)$/;
67
+ const windowsEnvironment = (env) => Object.fromEntries(Object.entries(env).filter(([name, value]) => !(rLocale.test(name.toUpperCase()) && posixUtf8.test(value ?? ''))));
40
68
  // On Windows a session's descriptor 3 reaches a program built on Microsoft's C runtime (Python),
41
69
  // but R has no way to name it. There R opens a named pipe served here, by the name it is given in
42
70
  // RHO_KERNEL_ANSWERS; the first connection is the session's and no other is taken.
@@ -104,6 +132,8 @@ export class KernelHost {
104
132
  placesWriting = Promise.resolve();
105
133
  ssh;
106
134
  serverctl;
135
+ // extensions.r and extensions.python: files a local session loads to describe what its plots draw
136
+ // (the figures plugin's). A session on a server has none: its plots stay pictures.
107
137
  constructor(root, options = {}) {
108
138
  this.root = root;
109
139
  this.options = options;
@@ -210,7 +240,10 @@ export class KernelHost {
210
240
  const [old] = this.cells.splice(index, 1);
211
241
  for (const output of old.outputs)
212
242
  if (output.kind === 'plot')
213
- await fs.rm(path.join(await this.plots(), `${old.cellId}-${output.plot}.png`), { force: true });
243
+ for (const end of ['.png', '.figure.json'])
244
+ await fs.rm(path.join(await this.plots(), `${old.cellId}-${output.plot}${end}`), { force: true });
245
+ for (const saved of old.saved ?? [])
246
+ await fs.rm(path.join(await this.plots(), `${old.cellId}-s${saved.number}.figure.json`), { force: true });
214
247
  }
215
248
  live.record.cells++;
216
249
  const cell = { cellId: id('k-'), changeId: input.changeId, by: input.by, sessionId: live.record.sessionId, language: input.language, code: input.code, purpose: input.purpose?.trim().slice(0, 200) || null,
@@ -269,7 +302,7 @@ export class KernelHost {
269
302
  const runner = source && `.cache/rho/kernel/runner-${digest(source).slice(0, 16)}${language === 'r' ? '.R' : '.py'}`;
270
303
  const pipe = windows && language === 'r' ? await answerPipe() : null;
271
304
  const child = place ? await this.transport(place).spawn(place, sessionCommand(place, language === 'r' ? 'Rscript' : 'python3', runner, source.length), root)
272
- : spawn(program, [runners[language], root], { cwd: root, ...ownGroup, stdio: ['pipe', 'pipe', 'pipe', 'pipe'], env: { ...process.env, MPLBACKEND: 'Agg', PYTHONUNBUFFERED: '1', ...(pipe ? { RHO_KERNEL_ANSWERS: pipe.name } : {}) } });
305
+ : spawn(program, [runners[language], root], { cwd: root, ...ownGroup, stdio: ['pipe', 'pipe', 'pipe', 'pipe'], env: { ...(windows ? windowsEnvironment(process.env) : process.env), MPLBACKEND: 'Agg', PYTHONUNBUFFERED: '1', ...(pipe ? { RHO_KERNEL_ANSWERS: pipe.name } : {}), ...(this.options.extensions?.[language]?.length ? { RHO_KERNEL_EXTENSIONS: this.options.extensions[language].join(path.delimiter) } : {}) } });
273
306
  let settle = () => { };
274
307
  const ended = new Promise(resolve => { settle = resolve; });
275
308
  const live = { started: selected, root, ended, settle, record, child, place, remotePid: null, answers: '', early: '', order: Promise.resolve(), queue: [], current: null, stopping: false, printed: '', mark: 0, held: [], answered: 0, looking: null, lookups: Promise.resolve(), pipe: pipe?.server };
@@ -523,8 +556,52 @@ export class KernelHost {
523
556
  else {
524
557
  if (data)
525
558
  await atomicBytes(path.join(plots, `${cell.cellId}-${number}.png`), data);
526
- cell.outputs.push({ kind: 'plot', plot: number, width: Number(msg.width) || 0, height: Number(msg.height) || 0 });
559
+ const output = { kind: 'plot', plot: number, width: Number(msg.width) || 0, height: Number(msg.height) || 0 };
560
+ const figure = path.join(plots, `${cell.cellId}-${number}.figure.json`);
561
+ if (!live.place && msg.figure && typeof msg.figure === 'object') {
562
+ const bytes = await fs.stat(figure).then(s => s.size, () => -1);
563
+ if (bytes < 0)
564
+ output.figureProblem = 'The figure record was not written.';
565
+ else if (bytes > this.limits.maxFigureBytes) {
566
+ await fs.rm(figure, { force: true });
567
+ output.figureProblem = `The figure record of ${bytes} bytes was larger than maxFigureBytes and was not kept.`;
568
+ }
569
+ else
570
+ output.figure = figureSummary(msg.figure);
571
+ }
572
+ else if (typeof msg.figureProblem === 'string')
573
+ output.figureProblem = msg.figureProblem.slice(0, 500);
574
+ cell.outputs.push(output);
575
+ }
576
+ this.soon();
577
+ return;
578
+ }
579
+ if (msg.kind === 'saved') {
580
+ // A file in the project the cell saved a plot to, read as the cell left it.
581
+ const number = Number(msg.number), relative = typeof msg.path === 'string' ? msg.path : '';
582
+ if (live.place || !Number.isSafeInteger(number) || number < 1 || number > this.limits.maxPlots || (cell.saved ?? []).some(s => s.number === number))
583
+ return;
584
+ const file = relative.length <= 512 ? await confined(this.root, relative).catch(() => null) : null;
585
+ const stat = file ? await fs.stat(file).catch(() => null) : null;
586
+ if (!file || !stat?.isFile())
587
+ return;
588
+ const { sha256, bytes } = await fileDigest(file);
589
+ const saved = { number, path: relative, bytes, sha256 };
590
+ const record = path.join(await this.plots(), `${cell.cellId}-s${number}.figure.json`);
591
+ if (msg.figure && typeof msg.figure === 'object') {
592
+ const size = await fs.stat(record).then(s => s.size, () => -1);
593
+ if (size < 0)
594
+ saved.figureProblem = 'The figure record was not written.';
595
+ else if (size > this.limits.maxFigureBytes) {
596
+ await fs.rm(record, { force: true });
597
+ saved.figureProblem = `The figure record of ${size} bytes was larger than maxFigureBytes and was not kept.`;
598
+ }
599
+ else
600
+ saved.figure = figureSummary(msg.figure);
527
601
  }
602
+ else
603
+ saved.figureProblem = typeof msg.figureProblem === 'string' ? msg.figureProblem.slice(0, 500) : 'Nothing described what this file draws.';
604
+ (cell.saved ??= []).push(saved);
528
605
  this.soon();
529
606
  return;
530
607
  }
@@ -764,6 +841,44 @@ export class KernelHost {
764
841
  const data = await fs.readFile(path.join(await this.plots(), `${cellId}-${plot}.png`));
765
842
  return { mediaType: 'image/png', data: data.toString('base64'), width: output.width, height: output.height };
766
843
  }
844
+ // The figure record an extension kept for a plot, as it wrote it; the figures plugin reads it.
845
+ async figureRecord(cellId, plot) {
846
+ await this.load();
847
+ const cell = this.cells.find(c => c.cellId === cellId);
848
+ requireThat(cell, 'not_found', 'This cell is not in the session record.');
849
+ const output = cell.outputs.find(o => o.kind === 'plot' && o.plot === plot);
850
+ requireThat(output, 'not_found', 'This cell kept no such plot.');
851
+ requireThat(output.figure, 'not_found', output.figureProblem ?? 'This plot is a picture only: nothing described what it draws.');
852
+ const file = path.join(await this.plots(), `${cellId}-${plot}.figure.json`);
853
+ const stat = await fs.stat(file).catch(() => null);
854
+ requireThat(stat && stat.size <= this.limits.maxFigureBytes, 'not_found', 'The figure record is no longer kept.');
855
+ return { cell: { cellId: cell.cellId, number: cell.number, by: cell.by, purpose: cell.purpose, code: cell.code, language: cell.language, endedAt: cell.endedAt, sessionId: cell.sessionId }, output: structuredClone(output), bytes: await fs.readFile(file) };
856
+ }
857
+ // The figure a cell kept for a file it saved, found by the file's bytes: the newest cell that saved
858
+ // those bytes. A file written since by anything else has other bytes and no figure.
859
+ async savedFigure(sha256) {
860
+ await this.load();
861
+ let problem;
862
+ for (const cell of [...this.cells].reverse())
863
+ for (const saved of [...(cell.saved ?? [])].reverse()) {
864
+ if (saved.sha256 !== sha256)
865
+ continue;
866
+ if (!saved.figure) {
867
+ problem ??= saved.figureProblem;
868
+ continue;
869
+ }
870
+ const file = path.join(await this.plots(), `${cell.cellId}-s${saved.number}.figure.json`);
871
+ const stat = await fs.stat(file).catch(() => null);
872
+ if (!stat || stat.size > this.limits.maxFigureBytes) {
873
+ problem ??= 'The figure record is no longer kept.';
874
+ continue;
875
+ }
876
+ return { cell: { cellId: cell.cellId, number: cell.number, by: cell.by, purpose: cell.purpose, code: cell.code, language: cell.language, endedAt: cell.endedAt, sessionId: cell.sessionId }, output: structuredClone(saved), bytes: await fs.readFile(file) };
877
+ }
878
+ // A file a cell saved but could not describe says why; one no cell saved is simply a picture.
879
+ requireThat(false, 'not_found', problem ?? 'No Notebook cell saved a figure as this file with these bytes: it is a picture only.', problem ? { saved: true } : {});
880
+ throw new Error('unreachable');
881
+ }
767
882
  // Closing ends the sessions: their variables live only in their processes.
768
883
  async close() {
769
884
  const exits = [...this.live.values()].map(live => new Promise(resolve => {
@@ -804,7 +919,8 @@ const content = [
804
919
  'To look at data, check a value or try a model, run code with kernel.run (language "r" or "python") instead of a one-off Rscript -e or python3 -c: what you define stays in the session for the next cell, and the person sees each cell, its output, its plots and the variables.',
805
920
  'Give each cell a short purpose. Pass waitMs to wait for it to finish; kernel.read reads it later.',
806
921
  'A session starts with the first cell of its language. Interrupt a cell that runs too long with kernel.interrupt; only the person stops a session.',
807
- 'Results meant to last still go to files from a script, so they can be run again without the session.',
922
+ 'Results meant to last still go to files from a script, so they can be rebuilt without the session. Run the script in the session (source("analysis.R"), exec(open("analysis.py").read())) rather than with Rscript or python3 from a shell: a figure file stays readable mark by mark only while it holds the bytes a cell saved.',
923
+ 'A ggplot drawn in the R session (ggplot2 and what is built on it: ggtree, enrichplot, Seurat, patchwork) is also kept as a figure whose drawn marks are bound to the rows they come from; so is a page drawn with base graphics (bound where its points match a table in the session) and a ComplexHeatmap (each cell named by its row, column and value). The plot output names its layers and marks. Read it with figure.read {cellId, plot} to check what was actually drawn (marks per layer, rows missing, mappings) and to get the rows behind marks. A ggplot saved to a project file with ggsave() is kept as a figure of that file too: the cell names it under saved, and figure.read {path} reads it while the file keeps those bytes. When the person selects marks in a plot or a saved figure, their message names the selection.',
808
924
  'Sessions can also run on a server. kernel.places lists the places and where each language runs; kernel.place.set names one (the server, its folder there, and setup lines run there before a session starts) or chooses it for R or Python; kernel.place.check connects and reports R and Python there.',
809
925
  'kernel.servers lists the servers the person manages with serverctl and the skills bound to each: how that server is used (its environments, where heavy work goes, what to avoid there). Before you name a place on one or run code there, read the skills that apply with kernel.server.skill and follow them, for example in the place\'s setup lines. They guide the work on that server; they do not widen what you may do. A place on such a server is reached through serverctl, which holds its login; another server is reached with ssh.',
810
926
  'The project\'s Python is the machine\'s python3 until you name another. When the work needs packages it lacks, make an environment in the project (python3 -m venv .venv or uv venv, then install into it) and pass interpreter: ".venv/bin/python" to kernel.run; later sessions of that language start with it too, and each cell records the program that ran it. A live session in which only you ran cells starts again with the program you name; once the person ran a cell in it, ask them to stop it first. Do not add another environment\'s packages to a running session\'s path.',
@@ -0,0 +1,14 @@
1
+ export interface SessionScope {
2
+ principal: string | null;
3
+ since: number | null;
4
+ at: number | null;
5
+ cellIds?: readonly string[];
6
+ }
7
+ type ScopedCell = {
8
+ cellId: string;
9
+ sessionId: string;
10
+ by: string;
11
+ queuedAt: string;
12
+ };
13
+ export declare function scopedCells<T extends ScopedCell>(all: T[], scope?: SessionScope): T[];
14
+ export {};
@@ -0,0 +1,9 @@
1
+ export function scopedCells(all, scope) {
2
+ if (!scope)
3
+ return all;
4
+ const exact = scope.cellIds === undefined ? null : new Set(scope.cellIds);
5
+ const sessions = new Set(all.filter(c => scope.principal !== null && c.by === scope.principal).map(c => c.sessionId));
6
+ return all.filter(c => ((exact ? exact.has(c.cellId) : sessions.has(c.sessionId))
7
+ || (c.by === 'browser:local' && scope.since !== null && Date.parse(c.queuedAt) >= scope.since))
8
+ && (scope.at === null || Date.parse(c.queuedAt) <= scope.at));
9
+ }
@@ -101,14 +101,33 @@ export interface UiSnapshot {
101
101
  title?: string | null;
102
102
  }>;
103
103
  }
104
- export interface SessionScope {
105
- principal: string | null;
106
- since: number | null;
107
- at: number | null;
104
+ export type { SessionScope } from './session-scope.js';
105
+ export interface FigureSlot {
106
+ client: UiClient;
107
+ projectId: string;
108
+ cellId?: string;
109
+ plot?: number;
110
+ file?: {
111
+ path?: string;
112
+ sha256: string;
113
+ };
114
+ width?: number;
115
+ height?: number;
116
+ label: string;
117
+ name?: string;
118
+ fit?: boolean;
119
+ cursor?: number;
120
+ fallback: ReactNode;
121
+ author?: (by: string) => {
122
+ name: string;
123
+ mine: boolean;
124
+ };
108
125
  }
109
126
  export interface KernelShell {
110
127
  highlight?(code: string, language: string): Promise<DocumentFragment | null>;
128
+ openPath?(path: string): void;
111
129
  markdown?(text: string): ReactNode;
130
+ figure?(slot: FigureSlot): ReactNode;
112
131
  }
113
132
  export declare const ShellProvider: import("react").Provider<KernelShell>;
114
133
  export declare const useShell: () => KernelShell;
@@ -123,4 +142,3 @@ export declare function when(iso: string, now?: Date): {
123
142
  text: string;
124
143
  full: string;
125
144
  };
126
- export {};
@@ -34,5 +34,5 @@ export function NotebookWorkSection({ work, snapshot, onOpen }) {
34
34
  const sessions = [...(read?.sessions ?? [])].filter(s => s.cells > 0)
35
35
  .sort((a, b) => Number(a.state === 'ended') - Number(b.state === 'ended') || b.startedAt.localeCompare(a.startedAt)).slice(0, notebookWorkLimits.sessions);
36
36
  const newest = [...(read?.cells ?? [])].reverse(), earlier = Math.max(0, cells - newest.length);
37
- return _jsxs("section", { className: "project-section notebook-work", "aria-labelledby": "notebook-work-title", children: [_jsxs("div", { className: "section-heading", children: [_jsx("h2", { id: "notebook-work-title", children: "Notebook" }), _jsx("button", { className: "text-button", onClick: onOpen, children: "Open Notebook" })] }), problem && _jsx("p", { role: "alert", children: problem }), sessions.length > 0 && _jsx("ul", { className: "notebook-work-sessions", children: sessions.map(s => _jsxs("li", { children: [_jsx(Icon, { name: s.place ? 'server' : 'code', size: 15 }), _jsxs("span", { children: [_jsx("strong", { children: versionOf(s) }), " ", s.place ? 'on ' + s.place.label : 'on this machine'] }), _jsxs("small", { children: [s.state === 'ended' ? 'ended' : s.state, " \u00B7 ", s.cells, " ", s.cells === 1 ? 'cell' : 'cells', s.state !== 'ended' ? ` · ${s.variables.length + s.variablesOmitted} ${s.variables.length + s.variablesOmitted === 1 ? 'value' : 'values'}` : ''] })] }, s.sessionId)) }), newest.length > 0 && _jsx("div", { className: "activity-list", children: newest.map(cell => _jsxs("button", { className: "activity-row", onClick: () => { askCell(cell.cellId); onOpen(); }, children: [_jsx("span", { className: 'activity-symbol' + (cell.state === 'running' || cell.state === 'queued' ? ' active' : ''), children: _jsx(Icon, { name: "code", size: 16 }) }), _jsxs("span", { className: "activity-name", children: [_jsx("strong", { children: cell.purpose || firstLine(cell.code) }), _jsxs("small", { children: [author(cell.by), " \u00B7 ", when(cell.endedAt ?? cell.startedAt ?? cell.queuedAt).text, stateWords[cell.state] ? ' · ' + stateWords[cell.state] : ''] })] }), _jsx(Icon, { name: "chevron", size: 13 })] }, cell.cellId)) }), earlier > 0 && _jsxs("p", { className: "quiet-empty", children: [earlier, " earlier ", earlier === 1 ? 'cell' : 'cells', " in the Notebook"] })] });
37
+ return _jsxs("section", { className: "project-section notebook-work", "aria-labelledby": "notebook-work-title", children: [_jsxs("div", { className: "section-heading", children: [_jsx("h2", { id: "notebook-work-title", children: "Notebook" }), _jsx("button", { className: "text-button", onClick: onOpen, children: "Open Notebook" })] }), problem && _jsx("p", { role: "alert", children: problem }), sessions.length > 0 && _jsx("ul", { className: "notebook-work-sessions", children: sessions.map(s => _jsxs("li", { children: [_jsx(Icon, { name: s.place ? 'server' : 'code', size: 15 }), _jsxs("span", { children: [_jsx("strong", { children: versionOf(s) }), " ", s.place ? 'on ' + s.place.label : s.state !== 'ended' ? 'on this machine' : ''] }), _jsxs("small", { children: [s.state === 'ended' ? 'ended' : s.state, " \u00B7 ", s.cells, " ", s.cells === 1 ? 'cell' : 'cells', s.state !== 'ended' ? ` · ${s.variables.length + s.variablesOmitted} ${s.variables.length + s.variablesOmitted === 1 ? 'value' : 'values'}` : ''] })] }, s.sessionId)) }), newest.length > 0 && _jsx("div", { className: "activity-list", children: newest.map(cell => _jsxs("button", { className: "activity-row", onClick: () => { askCell(cell.cellId); onOpen(); }, children: [_jsx("span", { className: 'activity-symbol' + (cell.state === 'running' || cell.state === 'queued' ? ' active' : ''), children: _jsx(Icon, { name: "code", size: 16 }) }), _jsxs("span", { className: "activity-name", children: [_jsx("strong", { children: cell.purpose || firstLine(cell.code) }), _jsxs("small", { children: [author(cell.by), " \u00B7 ", when(cell.endedAt ?? cell.startedAt ?? cell.queuedAt).text, stateWords[cell.state] ? ' · ' + stateWords[cell.state] : ''] })] }), _jsx(Icon, { name: "chevron", size: 13 })] }, cell.cellId)) }), earlier > 0 && _jsxs("p", { className: "quiet-empty", children: [earlier, " earlier ", earlier === 1 ? 'cell' : 'cells', " in the Notebook"] })] });
38
38
  }
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from 'react';
1
2
  import { type CellRelations } from './lineage.js';
2
3
  import { versionOf, type KernelShell, type SessionScope, type UiClient, type UiSnapshot, type KernelOutputs } from './ui-base.js';
3
4
  export { versionOf };
@@ -70,10 +71,10 @@ export declare function SessionPane({ client, snapshot, prepared, onTaken, onCon
70
71
  shell?: KernelShell;
71
72
  beside?: Beside;
72
73
  form?: NotebookForm;
73
- }): string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
74
+ }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
74
75
  export declare function NotebookPage({ shell, ...props }: Parameters<typeof NotebookBody>[0] & {
75
76
  shell?: KernelShell;
76
- }): string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
77
+ }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
77
78
  declare function NotebookBody({ client, snapshot, onConversation, onObject, scope, compact, inView, shown, prepared, onTaken, beside, form }: {
78
79
  client: UiClient;
79
80
  snapshot: UiSnapshot;
@@ -97,7 +98,8 @@ export declare function VariablesPane({ client, snapshot, onObject }: {
97
98
  export declare const plotsPaneLimits: {
98
99
  plots: number;
99
100
  };
100
- export declare function PlotsPane({ client, snapshot }: {
101
+ export declare function PlotsPane({ client, snapshot, shell }: {
101
102
  client: UiClient;
102
103
  snapshot: UiSnapshot;
103
- }): import("react").JSX.Element;
104
+ shell?: KernelShell;
105
+ }): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;