Skip to content

Commit 6c97da6

Browse files
authored
Merge pull request #2111 from mito-ds/dev
Release Dec 17, 2025
2 parents 0a9e54b + a480041 commit 6c97da6

File tree

85 files changed

+26787
-8843
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+26787
-8843
lines changed

.github/workflows/test-mitosheet-frontend.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,24 @@ jobs:
266266
cd tests
267267
source venv/bin/activate
268268
python dash-test.py &
269-
npm run test:dash
269+
npm run test:dash
270+
271+
test-mitosheet-jest:
272+
runs-on: ubuntu-24.04
273+
timeout-minutes: 30
274+
275+
steps:
276+
- uses: actions/checkout@v4
277+
- uses: actions/setup-node@v4
278+
with:
279+
node-version: 22
280+
cache: 'npm'
281+
cache-dependency-path: mitosheet/package-lock.json
282+
- name: Install dependencies
283+
working-directory: mitosheet
284+
run: |
285+
npm install
286+
- name: Run Jest tests
287+
working-directory: mitosheet
288+
run: |
289+
npm run test

conductor.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"scripts": {
3+
"setup": "cd mito-ai && (test -d venv || python3 -m venv venv) && source venv/bin/activate && pip install -e \".[test]\" && touch yarn.lock && jlpm install && jlpm build && jupyter labextension develop . --overwrite && jupyter server extension enable --py mito_ai",
4+
"run": "cd mito-ai && source venv/bin/activate && jlpm build && jupyter lab --browser=chrome"
5+
}
6+
}

mito-ai/mito_ai/completions/prompt_builders/agent_system_message.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from mito_ai.completions.prompt_builders.prompt_constants import (
55
CITATION_RULES,
6+
CELL_REFERENCE_RULES,
67
FILES_SECTION_HEADING,
78
JUPYTER_NOTEBOOK_SECTION_HEADING,
89
VARIABLES_SECTION_HEADING,
@@ -291,7 +292,7 @@ def create_agent_system_message_prompt(isChromeBrowser: bool) -> str:
291292
292293
Important information:
293294
1. The message is a short summary of the ALL the work that you've completed on this task. It should not just refer to the final message. It could be something like "I've completed the sales strategy analysis by exploring key relationships in the data and summarizing creating a report with three recommendations to boost sales.""
294-
2. The message should include citations for any insights that you shared with the user.
295+
2. The message should include citations for any insights that you shared with the user and cell references for whenever you refer to specific cells that you've updated or created.
295296
3. The next_steps is an optional list of 2 or 3 suggested follow-up tasks or analyses that the user might want to perform next. These should be concise, actionable suggestions that build on the work you've just completed. For example: ["Export the cleaned data to CSV", "Analyze revenue per customer", "Convert notebook into an app"].
296297
4. The next_steps should be as relevant to the user's actual task as possible. Try your best not to make generic suggestions like "Analyze the data" or "Visualize the results". For example, if the user just asked you to calculate LTV of their customers, you might suggest the following next steps: ["Graph key LTV drivers: churn and average transaction value", "Visualize LTV per age group"].
297298
5. If you are not sure what the user might want to do next, err on the side of suggesting next steps instead of making an assumption and using more CELL_UPDATES.
@@ -325,19 +326,29 @@ def create_agent_system_message_prompt(isChromeBrowser: bool) -> str:
325326
326327
RULES
327328
328-
- You are working in a Jupyter Lab environment in a .ipynb file.
329+
- You are working in a Jupyter Lab environment in a .ipynb file.
329330
- You can only respond with CELL_UPDATES or FINISHED_TASK responses.
330331
- In each message you send to the user, you can send one CellModification, one CellAddition, or one FINISHED_TASK response. BUT YOU WILL GET TO SEND MULTIPLE MESSAGES TO THE USER TO ACCOMPLISH YOUR TASK SO DO NOT TRY TO ACCOMPLISH YOUR TASK IN A SINGLE MESSAGE.
331332
- After you send a CELL_UPDATE, the user will send you a message with the updated variables, code, and files in the current directory. You will use this information to decide what to do next, so it is critical that you wait for the user's response after each CELL_UPDATE before deciding your next action.
332333
- When updating code, keep as much of the original code as possible and do not recreate variables that already exist.
333-
- When you want to display a dataframe to the user, just write the dataframe on the last line of the code cell instead of writing print(<dataframe name>). Jupyter will automatically display the dataframe in the notebook.
334334
- When writing the message, do not explain to the user how to use the CELL_UPDATE or FINISHED_TASK response, they will already know how to use them. Just provide a summary of your thought process. Do not reference any Cell IDs in the message.
335335
- When writing the message, do not include leading words like "Explanation:" or "Thought process:". Just provide a summary of your thought process.
336336
- When writing the message, use tickmarks when referencing specific variable names. For example, write `sales_df` instead of "sales_df" or just sales_df.
337337
338+
====
339+
340+
CODE STYLE
341+
342+
- Avoid using try/except blocks and other defensive programming patterns (like checking if files exist before reading them, verifying variables are defined before using them, etc.) unless there is a really good reason. In Jupyter notebooks, errors should surface immediately so users can identify and fix issues. When errors are caught and suppressed or when defensive checks hide problems, users continue running broken code without realizing it, and the agent's auto-error-fix loop cannot trigger. If a column doesn't exist, a file is missing, a variable isn't defined, or a module isn't installed, let it error. The user needs to know.
343+
- When you want to display a dataframe to the user, just write the dataframe on the last line of the code cell instead of writing print(<dataframe name>). Jupyter will automatically display the dataframe in the notebook.
344+
- When importing matplotlib, write the code `%matplotlib inline` to make sure the graphs render in Jupyter.
345+
338346
====
339347
{CITATION_RULES}
340348
349+
====
350+
{CELL_REFERENCE_RULES}
351+
341352
<Citation Example>
342353
343354
### User Message 1:
@@ -471,5 +482,4 @@ def create_agent_system_message_prompt(isChromeBrowser: bool) -> str:
471482
====
472483
473484
OTHER USEFUL INFORMATION:
474-
1. When importing matplotlib, write the code `%matplotlib inline` to make sure the graphs render in Jupyter
475-
2. The active cell ID is shared with you so that when the user refers to "this cell" or similar phrases, you know which cell they mean. However, you are free to edit any cell that you see fit."""
485+
1. The active cell ID is shared with you so that when the user refers to "this cell" or similar phrases, you know which cell they mean. However, you are free to edit any cell that you see fit."""

mito-ai/mito_ai/completions/prompt_builders/chat_system_message.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from mito_ai.completions.prompt_builders.prompt_constants import (
55
CHAT_CODE_FORMATTING_RULES,
6-
CITATION_RULES,
6+
CITATION_RULES,
7+
CELL_REFERENCE_RULES,
78
ACTIVE_CELL_ID_SECTION_HEADING,
89
CODE_SECTION_HEADING,
910
get_database_rules
@@ -28,6 +29,9 @@ def create_chat_system_message_prompt() -> str:
2829
====
2930
{CITATION_RULES}
3031
32+
====
33+
{CELL_REFERENCE_RULES}
34+
3135
<Example 1>
3236
{ACTIVE_CELL_ID_SECTION_HEADING}
3337
'7b3a9e2c-5d14-4c83-b2f9-d67891e4a5f2'
@@ -79,6 +83,18 @@ def create_chat_system_message_prompt() -> str:
7983
====
8084
{CHAT_CODE_FORMATTING_RULES}
8185
86+
====
87+
88+
CODE STYLE
89+
90+
- Avoid using try/except blocks and other defensive programming patterns (like checking if files exist before reading them, verifying variables are defined before using them, etc.) unless there is a really good reason. In Jupyter notebooks, errors should surface immediately so users can identify and fix issues. When errors are caught and suppressed or when defensive checks hide problems, users continue running broken code without realizing it, and the agent's auto-error-fix loop cannot trigger. If a column doesn't exist, a file is missing, a variable isn't defined, or a module isn't installed, let it error. The user needs to know.
91+
- Write code that preserves the intent of the original code shared with you and the task to complete.
92+
- Make the solution as simple as possible.
93+
- Do not add temporary comments like '# Fixed the typo here' or '# Added this line to fix the error'
94+
- When importing matplotlib, write the code `%matplotlib inline` to make sure the graphs render in Jupyter.
95+
96+
====
97+
8298
IMPORTANT RULES:
8399
- Do not recreate variables that already exist
84100
- Keep as much of the original code as possible
@@ -87,6 +103,5 @@ def create_chat_system_message_prompt() -> str:
87103
- Write code that preserves the intent of the original code shared with you and the task to complete.
88104
- Make the solution as simple as possible.
89105
- Reuse as much of the existing code as possible.
90-
- Do not add temporary comments like '# Fixed the typo here' or '# Added this line to fix the error'
91106
- Whenever writing Python code, it should be a python code block starting with ```python and ending with ```
92107
"""

mito-ai/mito_ai/completions/prompt_builders/prompt_constants.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@
4646
8. Do not include the citation in the code block as a comment. ONLY include the citation in the message field of your response.
4747
"""
4848

49+
CELL_REFERENCE_RULES = """RULES FOR REFERENCING CELLS
50+
51+
When referring to specific cells in the notebook in your messages, use cell references so the user can easily navigate to the cell you're talking about. The user sees cells numbered as "Cell 1", "Cell 2", etc., but internally cells are identified by their unique IDs.
52+
53+
To reference a cell, use this format inline in your message:
54+
[MITO_CELL_REF:cell_id]
55+
56+
This will be displayed to the user as a clickable "Cell N" link that navigates to the referenced cell.
57+
58+
Cell Reference Rules:
59+
60+
1. Use cell references when discussing specific cells you've created or modified (e.g., "I've added the data cleaning code in [MITO_CELL_REF:abc123]").
61+
2. Use cell references when referring to cells the user mentioned or that contain relevant context.
62+
3. The cell_id must be an actual cell ID from the notebook - do not make up IDs.
63+
4. Place the reference inline where it makes sense in your message, similar to how you would write "Cell 3" in natural language.
64+
5. Do not use cell references in code - only in the message field of your responses.
65+
6. Cell references are different from citations. Use citations for specific line-level insights; use cell references for general cell-level navigation.
66+
67+
Example:
68+
"I've loaded the sales data in [MITO_CELL_REF:c68fdf19-db8c-46dd-926f-d90ad35bb3bc] and will now calculate the monthly totals."
69+
"""
70+
4971
def get_active_cell_output_str(has_active_cell_output: bool) -> str:
5072
"""
5173
Used to tell the AI about the output of the active code cell.

mito-ai/mito_ai/completions/prompt_builders/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
3939
selected_files = [context["value"] for context in additional_context if context.get("type") == "file"]
4040
selected_db_connections = [context["value"] for context in additional_context if context.get("type") == "db"]
4141
selected_images = [context["value"] for context in additional_context if context.get("type", "").startswith("image/")]
42+
selected_cells = [context["value"] for context in additional_context if context.get("type") == "cell"]
4243

4344
# STEP 2: Create a list of strings (instructions) for each context type
4445
context_parts = []
@@ -66,6 +67,12 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
6667
"The following images have been selected by the user to be used in the task:\n"
6768
+ "\n".join(selected_images)
6869
)
70+
71+
if len(selected_cells) > 0:
72+
context_parts.append(
73+
"The following cells have been selected by the user to be used in the task:\n"
74+
+ "\n".join(selected_cells)
75+
)
6976

7077
# STEP 3: Combine into a single string
7178
return "\n\n".join(context_parts)

mito-ai/mito_ai/utils/open_ai_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ def get_open_ai_completion_function_params(
171171
"stream": stream,
172172
"messages": messages,
173173
}
174+
175+
if model == "gpt-5.2":
176+
completion_function_params["reasoning_effort"] = "low"
174177

175178
# If a response format is provided, we need to convert it to a json schema.
176179
# Pydantic models are supported by the OpenAI API, however, we need to be able to

mito-ai/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@
138138
"jupyterlab": {
139139
"extension": true,
140140
"outputDir": "mito_ai/labextension",
141-
"schemaDir": "schema"
141+
"schemaDir": "schema",
142+
"themePath": "style/theme/theme.css"
142143
},
143144
"eslintIgnore": [
144145
"node_modules",

mito-ai/src/Extensions/AiChat/ChatMessage/AssistantCodeBlock.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { CodeReviewStatus } from '../ChatTaskpane';
1313
import CodeIcon from '../../../icons/CodeIcon';
1414
import CodeBlockToolbar from './CodeBlockToolbar';
1515
import AgentComponentHeader from '../../../components/AgentComponents/AgentComponentHeader';
16+
import GoToCellIcon from '../../../icons/GoToCellIcon';
17+
import { NotebookPanel } from '@jupyterlab/notebook';
18+
import { scrollToCell, setActiveCellByIDInNotebookPanel } from '../../../utils/notebook';
1619

1720
interface IAssistantCodeBlockProps {
1821
code: string;
@@ -26,6 +29,8 @@ interface IAssistantCodeBlockProps {
2629
codeReviewStatus: CodeReviewStatus;
2730
agentModeEnabled: boolean;
2831
isErrorFixup?: boolean;
32+
cellId?: string;
33+
notebookPanel?: NotebookPanel | null;
2934
}
3035

3136
const AssistantCodeBlock: React.FC<IAssistantCodeBlockProps> = ({
@@ -40,10 +45,35 @@ const AssistantCodeBlock: React.FC<IAssistantCodeBlockProps> = ({
4045
codeReviewStatus,
4146
agentModeEnabled,
4247
isErrorFixup,
48+
cellId,
49+
notebookPanel,
4350
}) => {
4451
const [isCodeExpanded, setIsCodeExpanded] = useState(false);
4552
const shouldShowToolbar = isLastAiMessage || isCodeComplete;
4653

54+
const handleGoToCell = (e: React.MouseEvent): void => {
55+
e.stopPropagation();
56+
if (cellId && notebookPanel) {
57+
setActiveCellByIDInNotebookPanel(notebookPanel, cellId);
58+
scrollToCell(notebookPanel, cellId, undefined, 'center');
59+
}
60+
};
61+
62+
const renderActionButtons = (): React.ReactNode => {
63+
if (!cellId || !notebookPanel) {
64+
return null;
65+
}
66+
return (
67+
<button
68+
className="agent-component-header-action-button"
69+
onClick={handleGoToCell}
70+
title="Go to cell"
71+
>
72+
<GoToCellIcon />
73+
</button>
74+
);
75+
};
76+
4777
if (agentModeEnabled) {
4878

4979
// Handle regular code blocks
@@ -58,6 +88,7 @@ const AssistantCodeBlock: React.FC<IAssistantCodeBlockProps> = ({
5888
isExpanded={isCodeExpanded}
5989
displayBorder={!isErrorFixup}
6090
className={isErrorFixup ? 'error-fixup' : undefined}
91+
actionButtons={renderActionButtons()}
6192
/>
6293
{isCodeExpanded && (
6394
<PythonCode

mito-ai/src/Extensions/AiChat/ChatMessage/ChatDropdown.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import React, { useState, useEffect, useRef } from 'react';
77
import { ExpandedVariable } from './ChatInput';
88
import { getDatabaseConnections, getRules } from '../../../restAPI/RestAPI';
9-
import { VariableDropdownItem, FileDropdownItem, RuleDropdownItem } from './ChatDropdownItems';
9+
import { VariableDropdownItem, FileDropdownItem, RuleDropdownItem, CellDropdownItem } from './ChatDropdownItems';
10+
import { INotebookTracker } from '@jupyterlab/notebook';
11+
import { getAllCellReferences } from '../../../utils/cellReferences';
1012

1113
interface ChatDropdownProps {
1214
options: ExpandedVariable[];
@@ -16,6 +18,7 @@ interface ChatDropdownProps {
1618
isDropdownFromButton?: boolean;
1719
onFilterChange?: (filterText: string) => void;
1820
onClose?: () => void;
21+
notebookTracker?: INotebookTracker;
1922
}
2023

2124
interface ChatDropdownVariableOption {
@@ -38,11 +41,19 @@ interface ChatDropdownFileOption {
3841
file: ExpandedVariable;
3942
}
4043

44+
interface ChatDropdownCellOption {
45+
type: 'cell'
46+
cellNumber: number;
47+
cellId: string;
48+
cellType: string;
49+
}
50+
4151
export type ChatDropdownOption =
4252
| ChatDropdownVariableOption
4353
| ChatDropdownRuleOption
4454
| ChatDropdownFileOption
45-
| ChatDropdownDatabaseOption;
55+
| ChatDropdownDatabaseOption
56+
| ChatDropdownCellOption;
4657

4758
const priortizeByType = (options: ChatDropdownOption[], maxPerType: number): ChatDropdownOption[] => {
4859
/*
@@ -76,6 +87,7 @@ const ChatDropdown: React.FC<ChatDropdownProps> = ({
7687
isDropdownFromButton = false,
7788
onFilterChange,
7889
onClose,
90+
notebookTracker,
7991
}) => {
8092
const [selectedIndex, setSelectedIndex] = useState(0);
8193
const [localFilterText, setLocalFilterText] = useState(filterText);
@@ -107,16 +119,29 @@ const ChatDropdown: React.FC<ChatDropdownProps> = ({
107119
// Use local filter text when search input is shown, otherwise use prop
108120
const effectiveFilterText = isDropdownFromButton ? localFilterText : filterText;
109121

122+
// Get cell references if notebook tracker is available
123+
const cellReferences = notebookTracker?.currentWidget
124+
? getAllCellReferences(notebookTracker.currentWidget)
125+
: [];
126+
110127
// Create a list of all options with the format
111128
// ['type': 'variable', "expandedVariable": variable]
112129
// ['type': 'rule', "rule": rule]
113130
// ['type': 'file', "file": file]
131+
// ['type': 'cell', "cellNumber": number, "cellId": string]
114132
const allOptions: ChatDropdownOption[] = [
115133
// Rules first
116134
...rules.map((rule): ChatDropdownRuleOption => ({
117135
type: 'rule',
118136
rule: rule
119137
})),
138+
// Cells second (when user types @Cell or @cell)
139+
...cellReferences.map((cell): ChatDropdownCellOption => ({
140+
type: 'cell',
141+
cellNumber: cell.cellNumber,
142+
cellId: cell.cellId,
143+
cellType: cell.cellType
144+
})),
120145
// Files second
121146
...options
122147
.filter(variable => variable.file_name) // Only files
@@ -167,6 +192,12 @@ const ChatDropdown: React.FC<ChatDropdownProps> = ({
167192
} else if (option.type === 'db') {
168193
return option.variable.variable_name.toLowerCase().includes(effectiveFilterText.toLowerCase()) ||
169194
option.variable.value.toLowerCase().includes(effectiveFilterText.toLowerCase());
195+
} else if (option.type === 'cell') {
196+
// Match "CellN" (no space)
197+
const cellText = `cell${option.cellNumber}`;
198+
const numberText = String(option.cellNumber);
199+
return cellText.includes(effectiveFilterText.toLowerCase()) ||
200+
numberText.includes(effectiveFilterText.toLowerCase());
170201
} else {
171202
return option.rule.toLowerCase().includes(effectiveFilterText.toLowerCase());
172203
}
@@ -331,6 +362,20 @@ const ChatDropdown: React.FC<ChatDropdownProps> = ({
331362
/>
332363
);
333364
}
365+
case 'cell': {
366+
const uniqueKey = `cell-${option.cellNumber}`;
367+
return (
368+
<CellDropdownItem
369+
key={uniqueKey}
370+
cellNumber={option.cellNumber}
371+
cellId={option.cellId}
372+
cellType={option.cellType}
373+
index={index}
374+
selectedIndex={selectedIndex}
375+
onSelect={() => onSelect(option)}
376+
/>
377+
);
378+
}
334379
default:
335380
return null;
336381
}

0 commit comments

Comments
 (0)