@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.
- package/dist/agent-connect.d.ts +36 -2
- package/dist/agent-connect.js +64 -11
- package/dist/answer-board.js +3 -3
- package/dist/bridge.js +6 -1
- package/dist/browser-access.d.ts +1 -0
- package/dist/browser-access.js +5 -3
- package/dist/capabilities.d.ts +21 -5
- package/dist/capabilities.js +6 -4
- package/dist/cli.js +76 -11
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +13 -3
- package/dist/connect-cli.d.ts +50 -0
- package/dist/connect-cli.js +128 -0
- package/dist/contract.generated.d.ts +1191 -278
- package/dist/contract.generated.js +1 -1
- package/dist/contracts/owners.generated.js +845 -47
- package/dist/conversation-presentation.d.ts +9 -2
- package/dist/conversation-presentation.js +3 -1
- package/dist/conversations.d.ts +154 -8
- package/dist/conversations.js +147 -24
- package/dist/discovery.js +2 -2
- package/dist/extension-contract.d.ts +19 -0
- package/dist/extension-contract.js +15 -6
- package/dist/extension-markets.d.ts +74 -0
- package/dist/extension-markets.js +166 -0
- package/dist/extensions.d.ts +29 -1
- package/dist/extensions.js +13 -4
- package/dist/external-work.d.ts +22 -0
- package/dist/external-work.js +91 -0
- package/dist/plugin-cli.js +75 -3
- package/dist/plugin-inventory.d.ts +56 -0
- package/dist/plugin-inventory.js +41 -0
- package/dist/plugin-inventory.json +1581 -102
- package/dist/plugin-presentation.d.ts +105 -2
- package/dist/plugin-presentation.js +59 -5
- package/dist/plugins.d.ts +14 -3
- package/dist/plugins.js +12 -6
- package/dist/project-notes.js +1 -1
- package/dist/protocol.d.ts +6 -0
- package/dist/protocol.js +30 -12
- package/dist/replay.js +4 -0
- package/dist/response-contracts.js +26 -11
- package/dist/running.d.ts +10 -0
- package/dist/running.js +45 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +71 -8
- package/dist/state.d.ts +8 -0
- package/dist/state.js +8 -0
- package/dist/studio-layout.js +3 -3
- package/dist/task-frame.d.ts +1 -1
- package/dist/timeline-events.d.ts +1 -0
- package/dist/timeline-events.js +8 -1
- package/dist/ui-context.d.ts +73 -0
- package/dist/ui-context.js +98 -0
- package/dist/ui-contract.d.ts +23 -0
- package/dist/web/.vite/manifest.json +133 -74
- package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
- package/dist/web/assets/choices-D8vGwsl2.js +1 -0
- package/dist/web/assets/{console-Trqc0swD.js → console-Dyuo1q4N.js} +4 -4
- package/dist/web/assets/{dist-pF902jHA.js → dist-CSiCyBml.js} +1 -1
- package/dist/web/assets/{dist-DpIHRk3B.js → dist-D9FLb9Hm.js} +1 -1
- package/dist/web/assets/{dist-eMx6b8N1.js → dist-FXbTBwML.js} +1 -1
- package/dist/web/assets/{dist-DZmszrjG.js → dist-l3DUhbw5.js} +1 -1
- package/dist/web/assets/{explore-in-BCHqjJ7B.js → explore-in-BfSFGRh2.js} +1 -1
- package/dist/web/assets/graph-BU2_o5uA.js +2 -0
- package/dist/web/assets/index-C16mXVvk.css +1 -0
- package/dist/web/assets/index-CWg9XYXx.js +952 -0
- package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
- package/dist/web/assets/{notes-meaning-DcVGUBBY.js → notes-meaning-T2m5zQfv.js} +1 -1
- package/dist/web/assets/overview-PzfLJCo-.js +6 -0
- package/dist/web/assets/preview-BguEdsza.js +1 -0
- package/dist/web/assets/previews-pWNik-2g.js +3 -0
- package/dist/web/assets/{review-DYP0aCXb.js → review-Dy_85lcH.js} +1 -1
- package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
- package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
- package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
- package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
- package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
- package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
- package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
- package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
- package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
- package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
- package/dist/web/assets/{skills-1ec0Z-jx.js → skills-Dyz7mBlc.js} +1 -1
- package/dist/web/assets/{skills-work-DNexV4OJ.js → skills-work-BEGqlCkx.js} +1 -1
- package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
- package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
- package/dist/web/assets/work-turns-DSabCogR.js +1 -0
- package/dist/web/assets/{workspace-COmCbGV3.js → workspace-DfjBL--r.js} +2 -2
- package/dist/web/composition.json +138 -83
- package/dist/web/index.html +3 -3
- package/dist/web/renderer.json +1 -1
- package/dist/web-assets.js +2 -2
- package/dist/work-graph.d.ts +11 -1
- package/dist/work-graph.js +16 -5
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +8 -7
- package/docs/FRONTEND.md +53 -11
- package/docs/HEADLESS.md +104 -11
- package/docs/THIRD-PARTY-PLUGINS.md +75 -3
- package/node_modules/@rho-workspace/plugin-agent/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +31 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +45 -10
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +19 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
- package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
- package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
- package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
- package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
- package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
- package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
- package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
- package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
- package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
- package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
- package/node_modules/jose/package.json +1 -1
- package/package.json +6 -4
- package/protocol/index.json +91 -55
- package/protocol/operations/agent.sessions.list.json +13 -5
- package/protocol/operations/agent.sessions.read.json +13 -5
- package/protocol/operations/capability.list.json +382 -6
- package/protocol/operations/composition.apply.json +10 -2
- package/protocol/operations/composition.delete.json +845 -0
- package/protocol/operations/composition.read.json +10 -2
- package/protocol/operations/composition.undo.json +10 -2
- package/protocol/operations/conversation.agents.add.json +346 -0
- package/protocol/operations/conversation.agents.remove.json +340 -0
- package/protocol/operations/conversation.decide.json +196 -0
- package/protocol/operations/conversation.permission.json +196 -0
- package/protocol/operations/conversation.read.json +196 -0
- package/protocol/operations/conversation.send.json +265 -0
- package/protocol/operations/conversation.settings.check.json +213 -495
- package/protocol/operations/conversation.settings.read.json +211 -490
- package/protocol/operations/conversation.settings.update.json +235 -554
- package/protocol/operations/conversation.start.json +267 -5
- package/protocol/operations/conversation.stop.json +196 -0
- package/protocol/operations/extension.describe.json +39 -0
- package/protocol/operations/extension.install.json +62 -0
- package/protocol/operations/extension.list.json +164 -1
- package/protocol/operations/extension.market.change.json +210 -0
- package/protocol/operations/figure.read.json +265 -0
- package/protocol/operations/figure.select.json +157 -0
- package/protocol/operations/kernel.interrupt.json +119 -0
- package/protocol/operations/kernel.read.json +119 -0
- package/protocol/operations/kernel.run.json +119 -0
- package/protocol/operations/observation.snapshot.json +416 -8
- package/protocol/operations/project.openFile.json +10 -2
- package/protocol/operations/session.grant.json +4 -0
- package/protocol/operations/shared.json +1 -0
- package/protocol/operations/view.close.json +10 -2
- package/protocol/operations/view.protection.set.json +10 -2
- package/vendor/composition.json +177 -112
- package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
- package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
- package/web/theme.css +5 -0
- package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
- package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
- package/dist/web/assets/choices-D-YfnFP6.js +0 -1
- package/dist/web/assets/graph-lENgfQNH.js +0 -2
- package/dist/web/assets/index-B3_OVy91.css +0 -1
- package/dist/web/assets/index-BpL_Kl_s.js +0 -892
- package/dist/web/assets/overview-CDnVKlOt.js +0 -6
- package/dist/web/assets/preview-BEC1E9MK.js +0 -1
- package/dist/web/assets/studio-process-EYQgikxq.js +0 -2
- package/dist/web/assets/timeline-events-Chj2Gbx7.js +0 -1
- package/dist/web/assets/work-turns-CT9RVcWY.js +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.30.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
18
|
-
//
|
|
19
|
-
// maxWaitMs for its cell. What a session's own
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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<
|
|
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<
|
|
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
|
-
|
|
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;
|