Skip to content

Commit 1963e04

Browse files
committed
feat: Enhance chat permissions and approval modes
- Introduced native chat permissions mapping in cockpitWebviewSettingsHandler. - Updated settings handling to sync native approval modes with VS Code configuration. - Added experimental notice for GitHub integration in cockpitWebviewStrings and cockpitWebviewWorkspaceTabsMarkup. - Expanded ApprovalBootstrapMode to include "default", "auto-approve", and "autopilot". - Refactored syncApprovalMode function to streamline approval mode synchronization. - Updated tests to reflect changes in approval mode defaults.
1 parent 591d98a commit 1963e04

10 files changed

Lines changed: 11436 additions & 71 deletions

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ These are the default path and the main product surface.
109109

110110
`Todo Cockpit` is the planning and triage layer. A `Todo` stays a planning artifact: capture work, add comments, apply labels and workflow flags, and decide what should happen next.
111111

112-
Optional GitHub inbox triage also lives here. The `Settings` tab can save repo-local GitHub repository settings plus a reusable automation prompt, then expose a cached GitHub inbox at the top of the board with `Issues`, `Pull Requests`, and `Security Alerts`. Refresh uses your existing VS Code GitHub sign-in, inbox rows can create a plain Todo or `Create Todo + Review`, and repeat imports reuse the existing GitHub-sourced card instead of creating duplicates. For setup, storage, and current limits, see [docs/github-integration.md](https://github.com/goodguy1963/Copilot-Cockpit/blob/main/docs/github-integration.md).
112+
Optional, **experimental** GitHub inbox triage also lives here. The `Settings` tab can save repo-local GitHub repository settings plus a reusable automation prompt, then expose a cached GitHub inbox at the top of the board with `Issues`, `Pull Requests`, and `Security Alerts`. Refresh uses your existing VS Code GitHub sign-in, inbox rows can create a plain Todo or `Create Todo + Review`, and repeat imports reuse the existing GitHub-sourced card instead of creating duplicates. For setup, storage, current limits, and the road to stable, see [docs/github-integration.md](https://github.com/goodguy1963/Copilot-Cockpit/blob/main/docs/github-integration.md).
113113

114114
### Tasks
115115

@@ -133,6 +133,10 @@ Research is especially useful when work should pull in fresher outside knowledge
133133

134134
These capabilities stay discoverable, but they are not required for the default path.
135135

136+
- **GitHub Integration** (experimental): repo-local inbox triage for issues, pull requests, and security alerts. Read-only, manual refresh, no mutation support yet. See [docs/github-integration.md](https://github.com/goodguy1963/Copilot-Cockpit/blob/main/docs/github-integration.md).
137+
- **Telegram Notifications** (experimental): repo-local Stop hook that sends the last assistant reply to a Telegram bot.
138+
- **Codex integration** (experimental): repo-local MCP, skills, todo coordination, and task-draft coordination for ChatGPT Codex in VS Code.
139+
136140
### Model And Agent Choice
137141

138142
Copilot Cockpit is designed for mixed-model work. Sometimes one model is better for planning, another for implementation, and another for research or code review. The goal is not to crown one universal expert, but to let specialized agents and model choices work together under one controlled workflow.

docs/github-integration.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# GitHub Integration
22

3-
Copilot Cockpit includes an optional repo-local GitHub integration for Todo Cockpit. It lets you save repository settings in the `Settings` tab, manually refresh a cached GitHub inbox, and turn GitHub items into Todo cards or review-oriented handoffs without exposing a runtime access token back to the webview.
3+
> ⚠️ **Experimental** — This feature is experimental and may change or be removed in future versions.
4+
5+
Copilot Cockpit includes an optional, **experimental** repo-local GitHub integration for Todo Cockpit. It lets you save repository settings in the `Settings` tab, manually refresh a cached GitHub inbox, and turn GitHub items into Todo cards or review-oriented handoffs without exposing a runtime access token back to the webview.
46

57
This feature is intentionally narrow today:
68

@@ -147,12 +149,41 @@ That keeps the downstream task draft aligned with the earlier GitHub-aware revie
147149

148150
## Current Limits
149151

150-
- This is not a deep integration with the GitHub Pull Requests and Issues extension.
152+
This integration is **experimental and read-only**. The following capabilities are not yet implemented:
153+
154+
### Not Yet Implemented
155+
156+
- **Bidirectional sync**: You cannot create, update, or close GitHub issues, pull requests, or alerts from Copilot Cockpit. The integration is strictly read-only.
157+
- **Live push sync**: There is no webhook, event-driven, or polling-based automatic refresh. All inbox updates are manual.
158+
- **Deep GitHub extension integration**: This does not integrate with the GitHub Pull Requests and Issues extension. It operates independently through direct REST API calls.
159+
- **Issue/PR mutation**: You cannot comment on, assign, label, milestone, close, reopen, or merge GitHub items from within Cockpit.
160+
- **Discussions**: GitHub Discussions are not fetched or displayed.
161+
- **Workflow/actions visibility**: GitHub Actions workflow runs are not surfaced.
162+
- **Review integration**: Pull request reviews, review comments, and review status are not fetched.
163+
- **Notifications**: The GitHub notification inbox is not queried; only repository-scoped issues, PRs, and security alerts are fetched.
164+
165+
### Current Constraints
166+
151167
- Inbox sync is GitHub REST plus repo-local cached state.
152-
- The sync path is read-only.
153-
- Refresh is manual.
168+
- Each lane is capped at 50 items and 2 pages per refresh.
169+
- GitHub Enterprise refresh depends on VS Code's `github-enterprise` provider and a derivable server URI from `apiBaseUrl`.
154170
- There is no webhook or live push sync.
155-
- GitHub Enterprise refresh depends on VS Code's `github-enterprise` provider and a server URI that can be derived from the configured `apiBaseUrl` or is already configured in VS Code.
171+
- Rate limiting can cause partial or failed refreshes.
172+
- Only open issues and open pull requests are shown.
173+
- Security alerts cover code scanning alerts and Dependabot alerts only; secret scanning alerts are not included.
174+
175+
## Road To Stable
176+
177+
For this feature to graduate from experimental to stable, the following would be needed:
178+
179+
1. **Bidirectional mutation support** — Create, update, and close issues/PRs from Todo Cockpit, with audit trail.
180+
2. **Auto-refresh or webhook sync** — Optional polling interval or webhook receiver for live inbox updates.
181+
3. **GitHub Pull Requests and Issues extension integration** — Optional deep linking or co-operation with the official extension.
182+
4. **Expanded item coverage** — Discussions, workflow runs, secret scanning alerts, and notification inbox.
183+
5. **Robust rate-limit handling** — Backoff, retry, and graceful degradation across all lanes.
184+
6. **GitHub Enterprise hardening** — Explicit server URI configuration fallback, better error messaging for misconfigured Enterprise endpoints.
185+
7. **PR review context** — Fetch review status, comments, and requested reviewers for richer triage.
186+
8. **Stable API contract** — Lock down the stored schema, webview messages, and automation prompt contract so they don't change under users.
156187

157188
## Recommended Operator Workflow
158189

docs/integrations.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@
5757

5858
## GitHub Inbox Integration
5959

60-
- GitHub integration is an optional repo-local `Settings` tab feature for one repository in the current workspace.
60+
> ⚠️ **Experimental** — This integration is experimental and may change or be removed in future versions.
61+
62+
- GitHub integration is an optional, **experimental** repo-local `Settings` tab feature for one repository in the current workspace.
6163
- Inbox sync uses direct GitHub REST API reads plus repo-local cached state. It does not depend on the GitHub Pull Requests and Issues extension.
6264
- GitHub.com refresh uses VS Code's built-in `github` authentication provider.
6365
- A non-default GitHub API base URL routes refresh through VS Code's built-in `github-enterprise` authentication provider.

media/generated/cockpitWebview.js

Lines changed: 11285 additions & 14 deletions
Large diffs are not rendered by default.

src/cockpitWebviewSettingsHandler.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ const cockpitExtensionId = "local-dev.copilot-cockpit";
1919
const cockpitExtensionSettingsQuery = `@ext:${cockpitExtensionId}`;
2020
const copilotExtensionId = "github.copilot";
2121
const copilotExtensionSettingsQuery = `@ext:${copilotExtensionId}`;
22+
const chatPermissionsSettingKey = "chat.permissions.default";
23+
24+
type NativeChatPermissionsValue = "default" | "autoApprove" | "autopilot";
25+
26+
function toNativeChatPermissionsValue(
27+
approvalMode: ApprovalMode,
28+
): NativeChatPermissionsValue {
29+
switch (approvalMode) {
30+
case "auto-approve":
31+
case "yolo":
32+
return "autoApprove";
33+
case "autopilot":
34+
return "autopilot";
35+
case "default":
36+
default:
37+
return "default";
38+
}
39+
}
2240

2341
/** Handles settings/help messages that are routed out of the main webview controller. */
2442

@@ -80,11 +98,17 @@ export async function handleSettingsWebviewMessage(
8098
const safeMode = validModes.includes(approvalMode as ApprovalMode)
8199
? approvalMode as ApprovalMode
82100
: "default";
101+
const nativeApprovalMode = toNativeChatPermissionsValue(safeMode);
83102
await updateCompatibleConfigurationValue(
84103
"approvalMode",
85104
safeMode,
86105
vscode.ConfigurationTarget.Global,
87106
);
107+
await vscode.workspace.getConfiguration().update(
108+
chatPermissionsSettingKey,
109+
nativeApprovalMode,
110+
vscode.ConfigurationTarget.Global,
111+
);
88112
return true;
89113
}
90114
case "setStorageSettings": {

src/cockpitWebviewStrings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,11 @@ export function buildSchedulerWebviewStrings(
379379
"保存済み token を Webview に戻さずに、リポジトリローカルの GitHub リポジトリ設定、キャッシュされた Inbox 同期、再利用可能な自動化 prompt を構成します。",
380380
"Konfigurieren Sie repository-lokale GitHub-Repository-Einstellungen, gecachte Inbox-Synchronisierung und einen wiederverwendbaren Automatisierungs-prompt, ohne das gespeicherte token an das Webview zurückzugeben.",
381381
),
382+
githubIntegrationExperimentalNotice: localize(
383+
"This feature is experimental and may change or be removed in future versions.",
384+
"この機能は実験的であり、将来のバージョンで変更または削除される可能性があります。",
385+
"Diese Funktion ist experimentell und kann sich in zukünftigen Versionen ändern oder entfernt werden.",
386+
),
382387
githubIntegrationEnable: localize("Enable GitHub integration", "GitHub 連携を有効化", "GitHub-Integration aktivieren"),
383388
githubIntegrationOwner: localize("Owner", "Owner", "Owner"),
384389
githubIntegrationOwnerPlaceholder: localize("octocat", "octocat", "octocat"),

src/cockpitWebviewWorkspaceTabsMarkup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,7 @@ export function buildSchedulerWorkspaceTabsMarkup(options: {
705705
<div class="settings-card-header">
706706
<div class="section-title">GitHub ${escapeHtml(strings.githubIntegrationTitle)}</div>
707707
<p class="note">${escapeHtml(strings.githubIntegrationDescription)}</p>
708+
<p class="note" style="margin-top:4px;font-style:italic;opacity:0.8;">⚠️ ${escapeHtml(strings.githubIntegrationExperimentalNotice)}</p>
708709
</div>
709710
<div id="github-integration-feedback" class="telegram-feedback"></div>
710711

src/copilotExecutor.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ type PreparedExecution = {
5353
mustStartFresh: boolean;
5454
};
5555

56-
type ApprovalBootstrapMode = "off" | "yolo";
56+
type ApprovalBootstrapMode = "default" | "auto-approve" | "autopilot" | "yolo";
5757

5858
type HourlySessionBucket = {
5959
hourStartIso: string;
@@ -74,6 +74,21 @@ class HourlyChatSessionCapError extends Error {
7474
}
7575
}
7676

77+
function getApprovalBootstrapCommand(
78+
mode: ApprovalBootstrapMode,
79+
): string | undefined {
80+
switch (mode) {
81+
case "auto-approve":
82+
return "/auto-approve";
83+
case "autopilot":
84+
return "/autopilot";
85+
case "yolo":
86+
return "/yolo";
87+
default:
88+
return undefined;
89+
}
90+
}
91+
7792
const HOURLY_CHAT_SESSION_BUCKET_KEY = "copilotExecutor.hourlyNewChatSessionBucket";
7893
const PROMPT_EXECUTION_START_WINDOW_KEY = "copilotExecutor.promptExecutionStarts";
7994
const PROMPT_EXECUTION_LIMIT = 5;
@@ -354,7 +369,7 @@ async function offerPromptCopy(query: string): Promise<void> {
354369
export class CopilotExecutor {
355370
private static extensionContext: vscode.ExtensionContext | undefined;
356371
private static recentPromptExecutionStarts: number[] = [];
357-
private static approvalBootstrapMode: ApprovalBootstrapMode = "off";
372+
private static approvalBootstrapMode: ApprovalBootstrapMode = "default";
358373

359374
static configure(context?: vscode.ExtensionContext): void {
360375
CopilotExecutor.extensionContext = context;
@@ -364,6 +379,11 @@ export class CopilotExecutor {
364379
CopilotExecutor.approvalBootstrapMode = mode;
365380
}
366381

382+
static async syncNativeApprovalMode(mode: ApprovalBootstrapMode): Promise<void> {
383+
const executor = new CopilotExecutor();
384+
await executor.bootstrapFreshChatApprovalMode(mode, true);
385+
}
386+
367387
private prepareExecution(prompt: string, options?: ExecuteOptions): PreparedExecution {
368388
const expandedPrompt = this.expandPromptDirectives(prompt);
369389
const { mode, query } = getExecutionMode(expandedPrompt, options);
@@ -439,8 +459,20 @@ export class CopilotExecutor {
439459
}
440460
}
441461

442-
private async bootstrapFreshChatApprovalMode(): Promise<void> {
443-
if (CopilotExecutor.approvalBootstrapMode !== "yolo") {
462+
private async bootstrapFreshChatApprovalMode(
463+
mode: ApprovalBootstrapMode = CopilotExecutor.approvalBootstrapMode,
464+
createFreshChat = false,
465+
): Promise<void> {
466+
const approvalCommand = getApprovalBootstrapCommand(mode);
467+
468+
if (createFreshChat) {
469+
const created = await this.tryCreateNewChatSession();
470+
if (!created) {
471+
throw new Error("Unable to create a fresh Copilot Chat session for approval sync");
472+
}
473+
}
474+
475+
if (!approvalCommand) {
444476
return;
445477
}
446478

@@ -450,7 +482,7 @@ export class CopilotExecutor {
450482
}
451483

452484
await this.delay(DELAY_AFTER_FOCUS_MS);
453-
await vscode.commands.executeCommand(TYPE_COMMAND, { text: "/yolo" });
485+
await vscode.commands.executeCommand(TYPE_COMMAND, { text: approvalCommand });
454486
await this.delay(DELAY_AFTER_TYPE_MS);
455487

456488
const submitted = await this.executeFirstAvailableCommand(CHAT_SUBMIT_COMMANDS);

src/extension.ts

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,34 @@ function normalizeApprovalMode(value: string): ApprovalMode {
549549
}
550550
}
551551

552+
type ApprovalBootstrapModeValue = NonNullable<ApprovalMode>;
553+
554+
type ApprovalModeSyncOptions = {
555+
applyToCurrentSession?: boolean;
556+
};
557+
558+
type ApprovalModeSyncDeps = {
559+
setApprovalBootstrapMode: (mode: ApprovalBootstrapModeValue) => void;
560+
syncNativeApprovalMode: (mode: ApprovalBootstrapModeValue) => Promise<void>;
561+
};
562+
563+
async function syncApprovalModeForMode(
564+
value: string,
565+
options: ApprovalModeSyncOptions = {},
566+
deps: ApprovalModeSyncDeps = CopilotExecutor,
567+
): Promise<void> {
568+
const mode = normalizeApprovalMode(value);
569+
deps.setApprovalBootstrapMode(mode);
570+
if (options.applyToCurrentSession && mode !== "auto-approve") {
571+
await deps.syncNativeApprovalMode(mode);
572+
}
573+
}
574+
575+
type ApprovalModeSettingReader = (
576+
key: string,
577+
defaultValue: string,
578+
) => string;
579+
552580
function runPromptMaintenanceCycle(
553581
context: vscode.ExtensionContext,
554582
force: boolean,
@@ -2489,51 +2517,16 @@ async function runStartupSequence(
24892517
/**
24902518
* Sync the VS Code chat approval settings to match the configured approval mode.
24912519
*/
2492-
async function syncApprovalMode(): Promise<void> {
2493-
const mode = normalizeApprovalMode(getSchedulerSetting<string>("approvalMode", "default"));
2494-
const chatToolsConfig = vscode.workspace.getConfiguration("chat.tools");
2495-
const chatConfig = vscode.workspace.getConfiguration("chat");
2496-
2497-
const updateNativeApprovalSettings = async (
2498-
autoApprove: boolean,
2499-
autopilotEnabled: boolean,
2500-
): Promise<void> => {
2501-
await chatToolsConfig.update("autoApprove", autoApprove, vscode.ConfigurationTarget.Global);
2502-
await chatConfig.update("autopilot.enabled", autopilotEnabled, vscode.ConfigurationTarget.Global);
2503-
};
2504-
2505-
switch (mode) {
2506-
case "auto-approve":
2507-
CopilotExecutor.setApprovalBootstrapMode("off");
2508-
try {
2509-
await updateNativeApprovalSettings(true, false);
2510-
} catch (error) {
2511-
logExtensionErrorWithSanitizedDetails("[CopilotCockpit] Failed to sync approval mode:", error);
2512-
CopilotExecutor.setApprovalBootstrapMode("yolo");
2513-
}
2514-
return;
2515-
case "autopilot":
2516-
CopilotExecutor.setApprovalBootstrapMode("off");
2517-
try {
2518-
await updateNativeApprovalSettings(true, true);
2519-
} catch (error) {
2520-
logExtensionErrorWithSanitizedDetails("[CopilotCockpit] Failed to sync approval mode:", error);
2521-
CopilotExecutor.setApprovalBootstrapMode("yolo");
2522-
}
2523-
return;
2524-
case "yolo":
2525-
CopilotExecutor.setApprovalBootstrapMode("yolo");
2526-
break;
2527-
default:
2528-
CopilotExecutor.setApprovalBootstrapMode("off");
2529-
break;
2530-
}
2520+
async function syncApprovalMode(
2521+
settingReader: ApprovalModeSettingReader = getSchedulerSetting,
2522+
deps: ApprovalModeSyncDeps = CopilotExecutor,
2523+
): Promise<void> {
2524+
const mode = settingReader("approvalMode", "default");
25312525

2532-
try {
2533-
await updateNativeApprovalSettings(false, false);
2534-
} catch (error) {
2535-
logExtensionErrorWithSanitizedDetails("[CopilotCockpit] Failed to sync approval mode:", error);
2536-
}
2526+
// Only manage internal CopilotExecutor bootstrap state here; native chat
2527+
// permission settings remain owned by VS Code unless an explicit current-
2528+
// session sync path requests them.
2529+
await syncApprovalModeForMode(mode, {}, deps);
25372530
}
25382531

25392532
/**
@@ -3235,6 +3228,8 @@ export const __testOnly = {
32353228
ensureSchedulerSkillOnStartup,
32363229
getCurrentStorageSettings,
32373230
shouldSuppressSqliteWorkForExtensionContext,
3231+
syncApprovalMode,
3232+
syncApprovalModeForMode,
32383233
waitForSqliteStartupHydration,
32393234
};
32403235

src/test/suite/copilotExecutor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ suite("CopilotExecutor Test Suite", () => {
315315
mutableCommands.executeCommand = originalExecuteCommand;
316316
mutableWindow.showWarningMessage = originalShowWarningMessage;
317317
(CopilotExecutor as any).recentPromptExecutionStarts = [];
318-
CopilotExecutor.setApprovalBootstrapMode("off");
318+
CopilotExecutor.setApprovalBootstrapMode("default");
319319
}
320320
});
321321

0 commit comments

Comments
 (0)