Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d2013e9
Positron Notebooks: Add ghost cell suggestions feature
nstrayer Jan 30, 2026
5ef43c1
Fix ghost cell insertion position and document known TODOs
nstrayer Jan 30, 2026
94fa45f
Update ghost cell prompt to emphasize single-responsibility suggestions
nstrayer Jan 30, 2026
9f441e3
Improve ghost cell suggestion triggering and enable streaming updates
nstrayer Jan 30, 2026
5dfd2cb
Add SplitButton component for buttons with dropdown menus
nstrayer Jan 30, 2026
ba76e6f
Improve ghost cell and quick fix UX with split buttons
nstrayer Jan 30, 2026
fe50b65
Fix useMemo dependency warnings in NotebookCellQuickFix
nstrayer Jan 30, 2026
9a3d5fb
Add info button and modal dialog to ghost cell
nstrayer Jan 30, 2026
8d43420
Fix notebook metadata path and refactor settings update logic
nstrayer Jan 31, 2026
554c03d
Add option to disable ghost cell suggestions for the current notebook
nstrayer Jan 31, 2026
2d95bfb
Add configurable delay setting for ghost cell suggestions
nstrayer Jan 31, 2026
0e54833
Remove PRD from git tracking
nstrayer Jan 31, 2026
b1e32a0
ghost-cell-docs
nstrayer Feb 2, 2026
76bb524
Fix ghost cell left alignment to match regular cells
nstrayer Feb 2, 2026
2d60e46
Make ghost cell enable button provide instant feedback
nstrayer Feb 2, 2026
0923ae4
Rename ghost cell setting and link to it from info modal
nstrayer Feb 2, 2026
fbf1cc7
Add on-demand suggestion mode for ghost cells
nstrayer Feb 2, 2026
cf999c0
Use descriptive text for ghost cell settings link
nstrayer Feb 2, 2026
c272d23
Remove planning documents from git tracking
nstrayer Feb 2, 2026
a02483a
Show full ghost cell explanation on hover when truncated
nstrayer Feb 3, 2026
b32bfac
Add model setting for ghost cell suggestions
nstrayer Feb 4, 2026
7d9783d
Add model picker for ghost cell suggestions
nstrayer Feb 5, 2026
fd5eb10
Add command to re-enable ghost cell suggestions for a notebook
nstrayer Feb 5, 2026
7be6f45
Fix ghost cell disappearing after clicking Enable in opt-in prompt
nstrayer Feb 5, 2026
1cc6489
Fix ghost cell toggle styling to match ActionBarToggle pattern
nstrayer Feb 5, 2026
e18eeb0
Simplify ghost cell settings and improve UI
nstrayer Feb 5, 2026
663c061
Polish ghost cell UI: remove animation, speed up transitions, fix foc…
nstrayer Feb 5, 2026
9bc6443
Stop ignoring thoughts/ directory and add model picker task context
nstrayer Feb 5, 2026
6ab8d04
Remove thought/planning docs from PR
nstrayer Feb 5, 2026
2c94afd
Extract reusable SegmentedToggle component and simplify settings UI
nstrayer Feb 5, 2026
f8f1bb7
Document intentional duplication of notebookAssistantMetadata across …
nstrayer Feb 5, 2026
c55a0fc
Extract ghost cell into contrib/ghostCell module
nstrayer Feb 6, 2026
9e0d093
Update copyright year to 2026 in new ghost cell files
nstrayer Feb 6, 2026
e4b2d1d
Clean up meaningless diffs in notebook files
nstrayer Feb 6, 2026
6256337
Address PR review feedback: CSS polish and move UI files to contrib/g…
nstrayer Feb 9, 2026
de9c930
Flatten ghost cell header layout and fix button accessibility
nstrayer Feb 9, 2026
cbaaef2
Fix segmented toggle a11y and ghost cell lifecycle issues
nstrayer Feb 9, 2026
841d77b
Fix lint errors
nstrayer Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions extensions/positron-assistant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@
"title": "%commands.resetState.title%",
"category": "%commands.category%",
"enablement": "config.positron.assistant.enable"
},
{
"command": "positron-assistant.selectGhostCellModel",
"title": "%commands.selectGhostCellModel.title%",
"category": "%commands.category%",
"enablement": "config.positron.assistant.enable"
}
],
"configuration": [
Expand Down
1 change: 1 addition & 0 deletions extensions/positron-assistant/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"commands.managePromptFiles.title": "Manage Prompt Files",
"commands.collectDiagnostics.title": "Collect Diagnostics",
"commands.resetState.title": "Reset State",
"commands.selectGhostCellModel.title": "Select Ghost Cell Suggestion Model",
"commands.copilot.signin.title": "Copilot Sign In",
"commands.copilot.signout.title": "Copilot Sign Out",
"commands.category": "Positron Assistant",
Expand Down
117 changes: 117 additions & 0 deletions extensions/positron-assistant/src/commands/ghostCellModelPicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2026 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';

interface ModelQuickPickItem extends vscode.QuickPickItem {
modelId?: string;
isDefault?: boolean;
}

/**
* Opens a quick pick to select a model for ghost cell suggestions.
* Groups models by vendor and allows selecting "Use Default" to restore auto-select behavior.
*/
export async function selectGhostCellModel(): Promise<void> {
// Get all available models
const allModels = await vscode.lm.selectChatModels();

if (allModels.length === 0) {
vscode.window.showWarningMessage(
vscode.l10n.t('No language models available. Configure a model provider first.')
);
return;
}

// Get current setting to mark current selection
const config = vscode.workspace.getConfiguration('positron.assistant.notebook');
const currentPatterns = config.get<string[]>('ghostCellSuggestions.model') || [];
const currentModelId = currentPatterns.length > 0 ? currentPatterns[0] : null;
const isUsingDefault = !currentModelId ||
(currentPatterns.length === 2 &&
currentPatterns[0] === 'haiku' &&
currentPatterns[1] === 'mini');

// Group models by vendor
const modelsByVendor = new Map<string, vscode.LanguageModelChat[]>();
for (const model of allModels) {
const vendor = model.vendor;
if (!modelsByVendor.has(vendor)) {
modelsByVendor.set(vendor, []);
}
modelsByVendor.get(vendor)!.push(model);
}

// Build quick pick items
const items: ModelQuickPickItem[] = [];

// Add "Use Default" option at top
items.push({
label: isUsingDefault
? '$(check) Use Default (Auto-select)'
: 'Use Default (Auto-select)',
description: isUsingDefault ? '(current)' : undefined,
detail: 'Automatically selects a fast model (Haiku, Mini, etc.)',
isDefault: true
});

items.push({ label: '', kind: vscode.QuickPickItemKind.Separator });

// Add models grouped by vendor
const sortedVendors = Array.from(modelsByVendor.keys()).sort();
for (const vendor of sortedVendors) {
const models = modelsByVendor.get(vendor)!;

// Add vendor separator
items.push({
label: vendor,
kind: vscode.QuickPickItemKind.Separator
});

// Add models for this vendor
for (const model of models) {
const isCurrent = !isUsingDefault && currentModelId === model.id;
items.push({
label: isCurrent ? `$(check) ${model.name}` : model.name,
description: isCurrent ? '(current)' : undefined,
detail: model.id,
modelId: model.id
});
}
}

// Show quick pick
const selected = await vscode.window.showQuickPick(items, {
title: vscode.l10n.t('Select Model for Ghost Cell Suggestions'),
placeHolder: vscode.l10n.t('Choose a model or use default auto-selection'),
matchOnDetail: true
});

if (!selected) {
return; // User cancelled
}

// Update the setting
let newValue: string[];
let message: string;

if (selected.isDefault) {
newValue = ['haiku', 'mini'];
message = vscode.l10n.t('Ghost cell suggestions will use default model selection.');
} else if (selected.modelId) {
newValue = [selected.modelId];
message = vscode.l10n.t('Ghost cell suggestions will use {0}.', selected.label.replace('$(check) ', ''));
} else {
return; // Separator or invalid item
}

await config.update(
'ghostCellSuggestions.model',
newValue,
vscode.ConfigurationTarget.Global
);

vscode.window.showInformationMessage(message);
}
55 changes: 55 additions & 0 deletions extensions/positron-assistant/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { ALL_DOCUMENTS_SELECTOR, DEFAULT_MAX_TOKEN_OUTPUT } from './constants.js
import { registerCodeActionProvider } from './codeActions.js';
import { generateCommitMessage } from './git.js';
import { generateNotebookSuggestions, type NotebookActionSuggestion, type NotebookSuggestionsResult } from './notebookSuggestions.js';
import { generateGhostCellSuggestion, type GhostCellSuggestionResult } from './ghostCellSuggestions.js';
import { TokenUsage, TokenTracker } from './tokens.js';
import { exportChatToUserSpecifiedLocation, exportChatToFileInWorkspace } from './export.js';
import { AnthropicModelProvider } from './providers/anthropic/anthropicProvider.js';
import { registerParticipantDetectionProvider } from './participantDetection.js';
import { registerAssistantCommands } from './commands/index.js';
import { selectGhostCellModel } from './commands/ghostCellModelPicker.js';
import { PositronAssistantApi } from './api.js';
import { registerPromptManagement } from './promptRender.js';
import { collectDiagnostics } from './diagnostics.js';
Expand Down Expand Up @@ -324,6 +326,53 @@ function registerGenerateNotebookSuggestionsCommand(
);
}

function registerGenerateGhostCellSuggestionCommand(
context: vscode.ExtensionContext,
participantService: ParticipantService,
log: vscode.LogOutputChannel,
) {
context.subscriptions.push(
vscode.commands.registerCommand(
'positron-assistant.generateGhostCellSuggestion',
async (
notebookUri: string,
executedCellIndex: number,
progressCallbackCommand?: string,
skipConfigCheck?: boolean,
token?: vscode.CancellationToken
): Promise<GhostCellSuggestionResult | null> => {
// Create a token source only if no token is provided
let tokenSource: vscode.CancellationTokenSource | undefined;
const cancellationToken = token || (tokenSource = new vscode.CancellationTokenSource()).token;

// Progress callback handler that invokes the provided command
const onProgress = progressCallbackCommand
? (partial: Partial<GhostCellSuggestionResult>) => {
Promise.resolve(vscode.commands.executeCommand(progressCallbackCommand, partial)).catch(err => {
log.warn(`[ghost-cell] Progress callback failed: ${err}`);
});
}
: undefined;

try {
return await generateGhostCellSuggestion(
notebookUri,
executedCellIndex,
participantService,
log,
cancellationToken,
onProgress,
skipConfigCheck
);
} finally {
// Only dispose if we created the token
tokenSource?.dispose();
}
}
)
);
}

function registerExportChatCommands(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('positron-assistant.exportChatToFileInWorkspace', async () => {
Expand Down Expand Up @@ -410,6 +459,7 @@ function registerAssistant(context: vscode.ExtensionContext) {
registerConfigureModelsCommand(context);
registerGenerateCommitMessageCommand(context, participantService, log);
registerGenerateNotebookSuggestionsCommand(context, participantService, log);
registerGenerateGhostCellSuggestionCommand(context, participantService, log);
registerExportChatCommands(context);
registerToggleInlineCompletionsCommand(context);
registerCollectDiagnosticsCommand(context);
Expand All @@ -428,6 +478,11 @@ function registerAssistant(context: vscode.ExtensionContext) {
// Register chat commands
registerAssistantCommands();

// Register ghost cell model picker command
context.subscriptions.push(
vscode.commands.registerCommand('positron-assistant.selectGhostCellModel', selectGhostCellModel)
);

// Dispose cleanup
context.subscriptions.push({
dispose: () => {
Expand Down
Loading
Loading